ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Frappe Island 页头协议:页面岛如何向上报告标题与操作(决策 0010 深度解析)

Frappe Island 页头协议:页面岛如何向上报告标题与操作(决策 0010 深度解析) Frappe Island 页头协议页面岛如何向上报告标题与操作决策 0010 深度解析【免费下载链接】frappeLow code web framework for real world applications, in Python and Javascript项目地址: https://gitcode.com/GitHub_Trending/fr/frappe导读Frappe 的 Island 子系统让第三方应用如 CRM、Insights用 Vue 编写独立的页面岛page island嵌入 Desk 或 frappe-ui 应用两种宿主。本文聚焦决策文档 0010-a-page-island-reports-title-and-actions.md 定义的核心契约页面岛自身不渲染页头而是通过title与actions两个普通事件向上报告一个页头该说什么。读完本文你将理解这个契约的完整形状、两种宿主各自的消费方式Desk 的onTitle/onActions回调与 Vue 的title/actions绑定以及它为何否决了v-model、自绘页头、window.open与header: boolean四条备选路线。问题的起点页面岛与页头所有权分离一个页面岛page island填满整个页面例如在 Desk 中以独立路由承载整个视图。但它仍然需要一个页头page header一个标题以及属于当前内容的一组操作。从实现看岛自己知道这两样因为加载文档document的是它而页头属于宿主——Desk 每个页面都有页头frappe-ui 应用也有自己的LayoutHeader。决策 0010 由此定下分工页面岛不携带任何页头只报告页头会说什么。这是 Island 契约中职责归谁的典型案例岛负责业务内容宿主负责外围 chrome标题栏、菜单、面包屑。决策核心title与actions两个普通事件页面岛对外暴露两个上报字段title一个string或nullactions一个Action[]其中每个Action是{ label, icon? }外加onClick或href二者之一。关键语义划分如下字段含义执行方onClick在岛内部运行的动作岛自身href指向宿主应用之外页面的 URL绝对或站点相对均可宿主决定如何处理离开本应用的链接通常新开标签页href只负责陈述这个动作要去哪不负责执行如何离开。宿主在其自身惯用法中可以把该动作标记为离开应用的动作例如 Desk 的菜单行。这样设计避免了岛替宿主做决定详见下文被否决的方案三。此外契约约定了两种什么都不报告的情况占不满一页的岛如工作区里的 widget既不报告标题也不报告操作没有页头的宿主也不绑定这两个事件。Action字段恰好是一个岛需要的全部两个宿主都以菜单行的形式展示它们。因此新增一个字段就意味着两个宿主要同步变更——这正是把契约收敛到最少字段的原因。两种宿主各自消费互不知晓报告只是普通事件岛不需要知道自己运行在哪个宿主中Vue 宿主Island组件用title和actions绑定Desk 调用方通过onTitle和onActions回调接收。这与 0009-an-island-takes-vues-props-object.md 规定的props 对象契约一脉相承数据键与on*监听器键放在同一个扁平对象中与h(Component, props)的入参完全一致。以frappe.ui.mount_island为例Desk 加载器 展示了调用形状const island frappe.ui.mount_island(insights.dashboard, el, { dashboard: sales, onTitle: (title) frappe.utils.set_title(title || __(Dashboard)), }); island.update({ filters }); island.unmount();而 Vue 侧Island组件ui/island/Island.vue的文档示例同样清晰Island nameinsights.dashboard :dashboarddashboard :context{ user, locale, navigate } titletitle $event actionsactions $event navigaterouter.push($event) /Island组件是透明的除了name与context其余属性原样作为岛的 props 对象传给岛组件见 Island.vue因此title、actions与普通 Vue 组件上的事件绑定行为完全一致。因为上报走的是普通事件plain events宿主只绑定监听器、不向下传值Island的 props 过滤逻辑里也无需剔除任何update:形状的属性。宿主各自设置自己的 chrome两个宿主各自根据报告内容设置自己的外围界面Desk设置页面标题、填充页面菜单frappe-ui 应用填充其LayoutHeader。岛不需要知道自己在哪个宿主中每个宿主的页头都保持原生。Desk 侧实现pageview.js的set_island_chrome从源码层面看Desk 消费这两个事件的实现在 frappe/public/js/frappe/views/pageview.js。show_island()挂载岛时传入this.island frappe.ui.mount_island(this.pagedoc.island, this.island_container[0], { ...props, onTitle: (title) { this.island_title title; this.set_island_chrome(); }, onActions: (actions) { this.island_actions actions || []; this.set_island_chrome(); }, });set_island_chrome()随后执行三个动作标题写入最后一个面包屑与浏览器标签页使用frappe.breadcrumbs.add与frappe.utils.set_title而不是page.set_title——后者写入的.title-text面包屑会被下一次breadcrumbs.update()覆盖菜单清空与重建page.clear_menu()后遍历island_actions逐个page.add_menu_itemhref处理href指向 Desk 之外因此 Desk 用window.open(action.href, _blank)在新标签页打开onClick则直接作为点击处理器。注意 Desk 的菜单行本身不携带图标add_dropdown_item会写入自己的href#因此icon字段在 Desk 侧不读取。这也印证了契约里宿主决定链接出应用后做什么的职责划分Desk 对离开应用的href一律新开标签页。配套契约Desk 页面岛与文档声明0010 不是孤立的决策。Desk 页面岛的整套支撑链路包括Desk 页面岛0012-a-desk-page-can-be-an-island.mdPage增加typeFrappe UI 类型由岛绘制整个页面pageview.js负责挂载、传路由、并从岛的报告中设置页头文档认领0011-an-app-claims-a-desk-document-with-doc-events.md岛要绘制的文档在__onload.island中携带{name, props}由应用的doc_events的onload处理器写入doc_events {Dashboard: {onload: someapp.desk.island.dashboard}} def dashboard(doc, methodNone): if doc.someapp_dashboard: doc.set_onload(island, {name: someapp.dashboard, props: {dashboard: doc.someapp_dashboard}})框架统一构建0013-framework-builds-page-islands.md框架为 bench 上所有 Frappe UI 页面执行一次构建输出到sites/assets/frappe/dist/page-island/单一宿主循环0008-one-host-loop-two-hosts.md名称解析 → 导入模块 → 校验mount导出 → 卸载旧实例 → 调用mount这个循环只存在于 ui/island/host.jsDesk 加载器与Island都是它的薄包装。被否决的方案一update:事件与v-model绑定曾考虑使用update:title与update:actions使 Vue 宿主可以写v-model:title。否决理由语义错误v-model:actions展开后等于:actions加update:actions但岛根本没有声明actionsprop——绑定把一个值传进了一个忽略它的组件读起来像宿主与岛共享状态。实际上没有任何东西被共享岛报告、宿主存储这正是普通事件表达的语义成本v-model语法糖还让Island组件必须加一个过滤器剔除 props 对象中所有update:形状的属性否则宿主绑定的值会作为多余属性传回岛且每次报告都会通过update反弹。普通事件不向下传值过滤器随之消失。这一点与 0009 遥相呼应0009 明确记录岛 API 的事件名里不再有冒号正是 0010 删掉了仅有的两个冒号事件而update:title这类名字 Vue 会解析成字面键onUpdate:title监听器永远不触发且失败无报告。被否决的方案二岛自己画页头岛最先知道标题理论上可以先渲染页头。否决理由每个宿主已经有页头岛的页头会落在 Desk 页头下方Dashboard 页面当时为了给岛腾地方不得不隐藏 Desk 的页头代价是失去面包屑轨迹、页面菜单与标题槽——这三者随后又作为 context 传给岛、由岛重新渲染一遍而第二个宿主会把整套协商再重复一次。被否决的方案三岛自己打开外部应用曾设想离开到另一个应用的操作就是一个调用window.open的onClick。否决理由于是岛自己决定了新标签页以及命名目标地址的标签文案而这两者都属于宿主Desk 菜单行与站内应用的页头在视觉与语义上完全不同只有宿主知道当前展示的是哪一种SPA 宿主不得不通过一个 provider 标志告诉岛不要离开——这是岛在替它无法做出的决定做主而href只陈述目的地就停止干净利落。被否决的方案四header: booleanprop让调用方决定是否要页头。否决理由这个开关是在一个两种设置都错误的设计上打补丁——岛仍然携带页头代码每个宿主仍然要各自决定两条代码路径会逐渐漂移drift。小结决策 0010 确立的页头报告契约可以概括为三句话岛只报告不渲染titleactions两个普通事件onClick在岛内执行、href交由宿主决定如何离开宿主各自消费Desk 填充面包屑/标签页标题与页面菜单frappe-ui 应用填充LayoutHeader双方互不知晓对方存在最少字段、最小耦合两次否决v-model与header开关都是为了避免共享状态幻觉与双路漂移让契约停留在一个岛恰好需要的字段数。这套契约的完整实现分散在 ui/island/host.js宿主循环、ui/island/Island.vueVue 宿主、frappe/public/js/frappe/ui/island/loader.jsDesk 加载器与 frappe/public/js/frappe/views/pageview.jsDesk 页头消费中配合 ui/island/decisions/ 目录下的系列决策文档构成 Frappe 页面岛机制从决策到落地的完整闭环。【免费下载链接】frappeLow code web framework for real world applications, in Python and Javascript项目地址: https://gitcode.com/GitHub_Trending/fr/frappe创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表