Dify 前端 Step-by-step Tour 组件解析:跨路由新手引导的架构设计与实现 📅 发布时间:2026/9/7 4:56:00 👁 浏览次数: Dify 前端 Step-by-step Tour 组件解析跨路由新手引导的架构设计与实现【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/difyDify 控制台的 Step-by-step Tour分步引导是一个横跨多个页面的新手引导能力它以任务清单checklist驱动用户依次完成首页学习、应用创建、知识库构建与集成配置等核心路径并在每一步用聚光灯式的 Coachmark 高亮页面上的具体目标元素。本文基于仓库中 web/app/components/step-by-step-tour/README.md 对模块职责的划分结合 state.ts、mount.tsx、coachmark.tsx、target-registry.ts 等源码完整拆解这套引导系统的状态管理、目标注册、聚光灯几何计算与埋点追踪机制帮助读者理解一个跨路由 Tour 组件从服务端状态到像素级定位的完整实现链路。一、模块职责划分README 中的架构骨架README.md 首先明确了这个目录“owns the cross-route Step-by-step Tour capability”——它负责的是跨路由的引导能力各文件的职责划分如下文件职责state.ts持有服务端查询TanStack Query graph、内存中的 Tour 会话状态session以及领域命令domain commandsstorage.ts仅负责持久化的 shell 偏好展开/收起target-registry.ts负责引导目标guide target在各路由内容中的注册与查找mount.tsx组合清单checklist、Coachmark、导航与埋点coachmark.tsx负责 feature 专属的 portal、聚光灯几何spotlight geometry、指针阻断层pointer blockers与目标交互策略各路由页面route consumers仅解析自己页面数据相关的 guide 分支与目标元素README 还给出了两条关键设计约束服务端状态的唯一权威在 TanStack Query 缓存中。组件消费的是“窄派生 atomnarrow derived atoms 只写命令write-only commands”而不是一个混合了账户状态的门面facade。这是一种典型的“单一事实来源 最小派生面”的状态设计。清单checklist使用 Dify UI 的 Popover 原语Coachmark 是一个刻意的 feature 专属例外——因为通用 overlay 原语无法建模“在任意页面上开一个洞”的聚光灯效果所以它不是可复用的 overlay 原语。下面按这一骨架逐层展开。二、状态层 state.tsQuery 缓存 乐观命令 对账state.ts 是整个模块的状态中枢由三层构成2.1 服务端状态Query Atom服务端状态通过atomWithQuery挂到 TanStack Query 上const stepByStepTourStateQueryAtom atomWithQuery((get) consoleQuery.onboarding.stepByStepTour.state.get.queryOptions({ enabled: get(deploymentEditionAtom) CLOUD, }), ) const canonicalStepByStepTourStateAtom selectAtom( stepByStepTourStateQueryAtom, (query) query.data, )注意enabled条件只有deploymentEditionAtom CLOUD时才会发起请求——这意味着该 Tour 的状态接口仅对 Dify Cloud 部署版启用自托管版本不会拉取这份状态。canonicalStepByStepTourStateAtom用selectAtom从 query 中仅取出data字段这正是 README 所说“server state remains canonical in the TanStack Query cache”的落点。状态响应体来自dify/contracts/api/console/onboarding/types.gen生成的类型StepByStepTourStateResponse核心包含completed_task_ids、skipped、first_workspace_id、manually_enabled_workspace_ids、manually_disabled_workspace_ids等字段分别表示已完成任务、是否跳过、首个工作区以及手动启用/禁用的工作区集合。2.2 乐观更新pending commands 与领域命令每次写操作并不是直接改缓存而是先作为一个PendingStepByStepTourStateCommand入队再由stepByStepTourStateDataAtom把“权威数据 待处理命令”归约reduce成当前视图状态const stepByStepTourStateDataAtom atom((get) get(pendingStepByStepTourStateCommandsAtom).reduce( applyStepByStepTourStateCommand, get(canonicalStepByStepTourStateAtom), ), )applyStepByStepTourStateCommand对五种action做纯函数式的状态归约complete_task把task_id加入completed_task_idsaddId去重uncomplete_task从completed_task_ids移除removeIdskip置skipped: true并把当前工作区从manually_enabled_workspace_ids移除enable_current_workspace置skipped: false把当前工作区加入手动启用列表并从禁用列表移除disable_current_workspace反向操作。这种“命令入队 → 本地归约 → 异步提交 → 结算settle”的模式让 UI 获得即时反馈同时保证任何时刻的状态都是“服务端快照 本地命令序列”的可重放结果。2.3 结算与对账settleStepByStepTourStateCommandpatchStepByStepTourState是统一入口先把命令加入 pending 队列再调用consoleQuery.onboarding.stepByStepTour.state.patch发起变更。成功时用服务端返回的权威状态queryClient.setQueryData(...)覆盖缓存并移除对应命令失败时递增stepByStepTourStateReconciliationRevisionAtom并在没有其它 pending 命令时invalidateQueries重新拉取实现失败对账。settleStepByStepTourStateCommand中还有一个细节只有对账版本号未变化且队列为空时才把 revision 归零避免并发命令下的误判。对外暴露的领域命令都是以“write-only atom”形式提供例如skipStepByStepTourAtom、completeStepByStepTourTaskAtom、uncompleteStepByStepTourTaskAtom、enableStepByStepTourForCurrentWorkspaceAtom、disableStepByStepTourForCurrentWorkspaceAtom以及stepByStepTourStateUpdatingAtom判断是否有请求在途。2.4 内存会话session atom 与窄派生 atom与服务端持久状态分离的是纯内存的 Tour 会话export const stepByStepTourSessionAtom atomStepByStepTourSessionState( initialStepByStepTourSessionState, )StepByStepTourSessionState定义在 types.ts 中只有四个可选字段activeTaskId、activeGuideIndex、activeGuideGroup、activeGuideIndexes即“当前进行到哪个任务的哪一步引导”。围绕它有一组派生 atomactiveStepByStepTourTaskIdAtom、activeStepByStepTourGuideIndexAtom等和命令 atomstartStepByStepTourTaskAtom开始某任务重置 guide index 为 0advanceStepByStepTourGuideAtom推进到指定 guideresolveStepByStepTourGuideGroupAtom任务不变时切换 guide 分组如权限降级场景resetStepByStepTourSessionAtom清空会话。而派生的业务语义 atom 则把服务端状态翻译成组件可直接消费的窄接口例如stepByStepTourEnabledForCurrentWorkspaceAtom的判定逻辑见 state.ts当前工作区非空、全局未跳过、未被手动禁用且是首个工作区或在手动启用列表中才启用。这套“Query 缓存为权威、atom 只做窄派生”的结构正是 README 中“narrow derived atoms and write-only commands”的完整实现。三、持久化偏好 storage.ts只存一个 shell modestorage.ts 是模块中唯一触碰 localStorage 的文件且只存一件事——清单的外壳模式type StepByStepTourShellMode expanded | collapsed export const STEP_BY_STEP_TOUR_SHELL_MODE_STORAGE_KEY step-by-step-tour-shell-mode const [ _useStepByStepTourShellMode, useStepByStepTourShellModeValue, useSetStepByStepTourShellMode, ] createLocalStorageStateStepByStepTourShellMode( STEP_BY_STEP_TOUR_SHELL_MODE_STORAGE_KEY, expanded, { raw: true }, )基于 foxact 的createLocalStorageState默认值为expanded键名为step-by-step-tour-shell-mode。任务完成进度、跳过状态等绝不落 localStorage——它们全部走服务端状态。这一划分保证了多设备之间进度一致本地只保留“用户偏好把面板最小化”这类无状态含义的 UI 选择。四、目标注册表 target-registry.ts跨路由的锚点协议Tour 要跨越/、/apps、/datasets、集成页等多个路由就必须回答一个问题各路由页面中的“目标元素”如何被统一寻址答案是data-step-by-step-tour-target属性协议加上一个集中式常量注册表target-registry.tsexport const STEP_BY_STEP_TOUR_TARGETS { home: step-by-step-tour-home, homeTryAppCreate: step-by-step-tour-home-try-app-create, studioEmptyTemplate: step-by-step-tour-studio-empty-template, studioWithAppsCreate: step-by-step-tour-studio-with-apps-create, knowledgeEmptyCreate: step-by-step-tour-knowledge-empty-create, integrationModelProviderNav: step-by-step-tour-integration-model-provider-nav, integrationMcpAdd: step-by-step-tour-integration-mcp-add, // … 共 45 个目标常量 } as const配套的查询函数把目标名映射成属性选择器export function getStepByStepTourTargetSelector(target: string) { return [data-step-by-step-tour-target${target}] }因此任何路由页面想成为引导目标只需在自己的 DOM 元素上加data-step-by-step-tour-target{STEP_BY_STEP_TOUR_TARGETS.xxx}as const的注册表让 IDE 与类型系统能穷举所有合法目标避免字符串散落。4.1 引导分组Guide Group与任务定义同一个任务在不同页面状态下需要不同的引导序列这就引入了 types.ts 中的StepByStepTourGuideGroupexport type StepByStepTourGuideGroup | homeNoCreate | studioEmpty | studioWithApps | studioNoCreateEmpty | studioNoCreateWithApps | knowledgeEmpty | knowledgeWithDatasets | integrationLimitedAccess分组名本身就编码了“页面数据状态”例如studioEmpty应用列表为空走“模板/空白/DSL/学习 Dify”四步引导而studioWithApps已有应用只走“创建 管理第一个应用”两步。getStepByStepTourGuides(taskId, guideGroup)是分组到 guide 数组的解析函数对home只在homeNoCreate时返回受限引导对studio/knowledge必须命中对应分组否则返回空数组对integration在integrationLimitedAccess时返回精简版其余任务直接取STEP_BY_STEP_TOUR_GUIDES[taskId]。四个任务的静态定义在 tasks.ts任务 id路由学习文档路径权限兜底home//use-dify/getting-started/introduction—canClickThrough: truestudio/apps/use-dify/workspace/app-management—knowledge/datasets—show-disabled-reasonintegrationbuildIntegrationPath(provider)/use-dify/workspace/pluginsshow-disabled-reason每个任务定义StepByStepTourTaskDefinition还带有iconClassName、fallbackTarget与canClickThrough字段后者决定该任务的 action 型引导是否允许“只阻断目标以外区域、允许点目标本身”的交互策略。4.2 Guide 的两种类型与交互策略StepByStepTourGuide的每个条目以 i18n keyI18nKeysWithPrefixcommon, stepByStepTour.引用标题与文案并带两个行为维度类型kindaction要求用户真的去点击目标与walkthrough讲解型步骤。getStepByStepTourGuideKind的默认规则是显式kind优先否则completionMode external判为action其余为walkthrough。例如home.create_app是completionMode: external的 action——任务完成不靠“下一步”按钮而依赖用户真实创建了应用交互策略interactionPolicyblocked全屏阻断与target-only只阻断目标外、目标可点击。getStepByStepTourGuideInteractionPolicy的默认推导是显式声明优先action 型 guide 在任务canClickThrough为真时取target-only否则blockedwalkthrough 型一律blocked。此外guide 还可以声明highlightPartSelectors把目标之外的浮动菜单等元素并入聚光灯、integrationSection集成页内部子导航的自动跳转、optional目标不存在时可跳过与portalOrder: afterOverlaysportal 层叠顺序。五、组合层 mount.tsx权限降级、步骤推进与埋点mount.tsx 是 README 说的“compose the checklist, coachmarks, navigation, and analytics”的落点也是权限敏感逻辑最集中的地方。5.1 可见性判定组件先计算三个由工作区权限派生的 guide 分组const canCreateApp hasPermission(workspacePermissionKeys, app.create_and_management) const homeGuideGroup canCreateApp ? undefined : homeNoCreate const hasKnowledgeWalkthroughPermissions hasPermission(workspacePermissionKeys, dataset.create_and_management) hasPermission(workspacePermissionKeys, dataset.external.connect) const integrationGuideGroup isCurrentWorkspaceManager ? undefined : integrationLimitedAccess即无创建应用权限的用户在首页看到的是homeNoCreate引导非工作区管理员进入集成任务时降级为integrationLimitedAccess引导。整体可见性条件为deployment_edition CLOUD且系统特性enable_step_by_step_tour开启且当前工作区启用另有一条隐藏规则——路径以/app/开头或包含/installed/时进入具体应用内部隐藏 Tour。任务列表还会依据enable_learn_app特性动态裁剪关闭“学习应用”时剔除home任务mount.tsx。5.2 步骤推进的核心逻辑completeActiveGuide实现了 walkthrough 的步进状态机completionMode external的 guide 不响应“下一步”——完成只能由外部行为如真实创建应用触发若还有下一个 guide则通过getNextVisibleActiveGuideIndex寻找下一个可见guide——该函数会跳过计划不允许isGuideEligibleForPlan例如无plugin.plugin_preferences权限时排除integrationUpdateSettings这一步或目标元素不存在isOptionalGuideTargetAvailable用document.querySelector探测的 guide没有下一个可见 guide 时调用completeTask完成任务、重置会话并把 shell 恢复为展开。集成任务还有自动路由联动当 active guide 声明了integrationSection且当前不在对应集成子路由时useEffect会router.push(buildIntegrationPath(...))把用户带过去并用 ref 记录防止重复跳转。同时 active 目标元素若不在视口内scrollTourTargetIntoView会scrollIntoView({ block: nearest })。5.3 清单 UI 与跳过恢复清单本体是 Dify UI Popover锚点为底部一个h-0 w-full的隐藏 divPopoverPositioner以placementtop-start定位最小化后只剩一个MinimizedTourPill触发器二者配合实现 README 所指的“checklist uses Dify UI Popover”。“跳过”走带退出的两段式skipTour先置checklistExiting160ms 透明度过渡再调用patchSkipTour即skipStepByStepTourAtom若服务端调用失败则回滚退出动画并隐藏恢复提示若成功且提供了recoveryAnchorRef则弹出SkipRecoveryPrompt挂在帮助菜单锚点上的小 Popover允许用户“反悔重新启用”——对应 state 层的enableStepByStepTourForCurrentWorkspaceAtom。5.4 埋点体系analytics.ts 定义统一事件名step_tour通过 AmplitudetrackEvent上报。动作枚举覆盖完整生命周期tour_shown带entry_pointfirst_workspace/help_menu_enabled/reenabled_after_skip、guide_shown/guide_completed/guide_skipped、task_started/task_completed/task_reopened、tour_completed/tour_skipped以及permission_fallback_shown。getStepByStepTourPermissionVariant把用户权限状态归约为full/no_create/no_knowledge_permission/no_integration_permission四个变体随事件上报用于分析不同权限用户的引导体验。mount 内用多个 refshownAnalyticsKeyRef、stepShownAnalyticsKeyRef等对事件按“工作区:入口”或“工作区:任务:guide”去重保证同一 guide 不重复上报。六、coachmark.tsx聚光灯几何与指针阻断的实现coachmark.tsx 是 README 强调的“deliberate feature-owned exception”。它没有复用通用 overlay 原语而是自己用 portal 渲染三层结构。6.1 Portal 根节点与层叠顺序useStepByStepTourPortalRoot动态创建一个带data-step-by-step-tour-portal-root属性的 div 挂到document.body并在每次渲染后若仍有兄弟节点则重新 append 到 body 末尾——从源码结构看这是为了持续保持在最上层。当 guide 声明portalOrder: afterOverlays如首页home.create_app时才会启用这个延迟 portal 根普通 coachmark 直接挂在document.body。6.2 高亮洞与四块 pointer blocker聚光灯视觉核心是一个高亮 divdiv contenteditable="false">【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考