# 反馈组件 **本文引用的文件** - [modal.vue](file://fms-vue/src/components/ui/modal/modal.vue) - [drawer.vue](file://fms-vue/src/components/ui/drawer/drawer.vue) - [message.vue](file://fms-vue/src/components/ui/message/message.vue) - [notification.vue](file://fms-vue/src/components/ui/notification/notification.vue) - [popover.vue](file://fms-vue/src/components/ui/popover/popover.vue) - [tooltip.vue](file://fms-vue/src/components/ui/tooltip/tooltip.vue) - [focus.js](file://fms-vue/src/components/ui/utils/focus.js) - [scroll.js](file://fms-vue/src/components/ui/utils/scroll.js) - [drag.js](file://fms-vue/src/components/ui/utils/drag.js) - [position.js](file://fms-vue/src/components/ui/utils/position.js) ## 目录 1. [简介](#简介) 2. [项目结构](#项目结构) 3. [核心组件](#核心组件) 4. [架构总览](#架构总览) 5. [详细组件分析](#详细组件分析) 6. [依赖关系分析](#依赖关系分析) 7. [性能与可访问性](#性能与可访问性) 8. [异步操作与错误处理](#异步操作与错误处理) 9. [动画、定位与多语言](#动画定位与多语言) 10. [故障排查](#故障排查) 11. [结论](#结论) ## 简介 本章节面向 FMS 前端组件库中的“用户反馈”类组件,涵盖模态框(Modal)、抽屉(Drawer)、消息提示(Message)、通知(Notification)、气泡卡片(Popover)与工具提示(Tooltip)。文档从设计理念、触发机制、生命周期管理、状态控制、选择策略、交互原则、异步反馈与错误处理、动画效果、定位算法、多语言支持等维度进行系统化说明,帮助开发者在不同场景下正确选用并组合这些组件。 ## 项目结构 反馈组件集中在 fms-vue 的 ui 目录下,按功能划分: - 弹层容器与交互:modal、drawer、popover、tooltip - 全局轻提示与通知:message、notification - 通用能力:focus(焦点陷阱)、scroll(滚动锁)、drag(拖拽/缩放)、position(浮层定位) ```mermaid graph TB subgraph "弹层与浮层" M["Modal"] D["Drawer"] P["Popover"] T["Tooltip"] end subgraph "全局反馈" MSG["Message"] NTF["Notification"] end subgraph "通用能力" F["focus.js"] S["scroll.js"] G["drag.js"] POS["position.js"] end M --> F M --> S M --> G D --> F D --> S D --> G P --> POS T --> P MSG --> |渲染| MSG NTF --> |渲染| NTF ``` 图表来源 - [modal.vue:1-291](file://fms-vue/src/components/ui/modal/modal.vue#L1-L291) - [drawer.vue:1-219](file://fms-vue/src/components/ui/drawer/drawer.vue#L1-L219) - [popover.vue:1-294](file://fms-vue/src/components/ui/popover/popover.vue#L1-L294) - [tooltip.vue:1-59](file://fms-vue/src/components/ui/tooltip/tooltip.vue#L1-L59) - [message.vue:1-77](file://fms-vue/src/components/ui/message/message.vue#L1-L77) - [notification.vue:1-82](file://fms-vue/src/components/ui/notification/notification.vue#L1-L82) - [focus.js:1-63](file://fms-vue/src/components/ui/utils/focus.js#L1-L63) - [scroll.js:1-42](file://fms-vue/src/components/ui/utils/scroll.js#L1-L42) - [drag.js:1-202](file://fms-vue/src/components/ui/utils/drag.js#L1-L202) - [position.js:1-99](file://fms-vue/src/components/ui/utils/position.js#L1-L99) 章节来源 - [modal.vue:1-291](file://fms-vue/src/components/ui/modal/modal.vue#L1-L291) - [drawer.vue:1-219](file://fms-vue/src/components/ui/drawer/drawer.vue#L1-L219) - [popover.vue:1-294](file://fms-vue/src/components/ui/popover/popover.vue#L1-L294) - [tooltip.vue:1-59](file://fms-vue/src/components/ui/tooltip/tooltip.vue#L1-L59) - [message.vue:1-77](file://fms-vue/src/components/ui/message/message.vue#L1-L77) - [notification.vue:1-82](file://fms-vue/src/components/ui/notification/notification.vue#L1-L82) - [focus.js:1-63](file://fms-vue/src/components/ui/utils/focus.js#L1-L63) - [scroll.js:1-42](file://fms-vue/src/components/ui/utils/scroll.js#L1-L42) - [drag.js:1-202](file://fms-vue/src/components/ui/utils/drag.js#L1-L202) - [position.js:1-99](file://fms-vue/src/components/ui/utils/position.js#L1-L99) ## 核心组件 - Modal:居中或顶部对齐的对话框,支持受控/非受控开关、遮罩关闭、Esc 关闭、标题/内容/底部插槽、确定/取消按钮、加载态、全屏切换、拖拽与四边缩放、焦点陷阱与滚动锁定、ARIA 无障碍。 - Drawer:贴边滑出面板,支持四方向 placement、尺寸可调、三段式布局(header/body/footer)、焦点与滚动管理。 - Message:页面顶部居中的轻量提示,自动消失,支持类型与手动关闭。 - Notification:右上角通知卡片,承载标题+描述,支持手动关闭与自动消失、堆叠重排。 - Popover:基于触发器的浮层,支持多种触发方式、智能定位(flip/shift)、箭头、跟随宽度等。 - Tooltip:对 Popover 的 hover 薄封装,提供轻量文本提示。 章节来源 - [modal.vue:1-291](file://fms-vue/src/components/ui/modal/modal.vue#L1-L291) - [drawer.vue:1-219](file://fms-vue/src/components/ui/drawer/drawer.vue#L1-L219) - [message.vue:1-77](file://fms-vue/src/components/ui/message/message.vue#L1-L77) - [notification.vue:1-82](file://fms-vue/src/components/ui/notification/notification.vue#L1-L82) - [popover.vue:1-294](file://fms-vue/src/components/ui/popover/popover.vue#L1-L294) - [tooltip.vue:1-59](file://fms-vue/src/components/ui/tooltip/tooltip.vue#L1-L59) ## 架构总览 反馈组件采用“声明式组件 + 命令式管理器”的组合模式: - Modal/Drawer/Popover/Tooltip:以组件形式声明使用,内部通过 Teleport 挂载到 body,结合 Transition 实现动画;Modal/Drawer 共享 focus/scroll/drag 能力。 - Message/Notification:提供命令式 API(如 success/open),在首次调用时创建单例容器挂载至 body,内部维护活跃条目数组并使用 TransitionGroup 完成堆叠与过渡。 ```mermaid sequenceDiagram participant U as "业务代码" participant M as "Modal/Drawer" participant F as "focus.js" participant S as "scroll.js" participant G as "drag.js" U->>M : 打开(open=true) M->>S : lockScroll() M->>F : focusFirst(panelRef) M->>G : bindDrag/bindResize(可选) Note over M : 键盘监听/ESC关闭/焦点陷阱生效 U->>M : 关闭(open=false) M->>F : trapFocus解除 M->>G : destroy() M->>S : unlockScroll() ``` 图表来源 - [modal.vue:118-198](file://fms-vue/src/components/ui/modal/modal.vue#L118-L198) - [drawer.vue:106-154](file://fms-vue/src/components/ui/drawer/drawer.vue#L106-L154) - [focus.js:22-63](file://fms-vue/src/components/ui/utils/focus.js#L22-L63) - [scroll.js:15-42](file://fms-vue/src/components/ui/utils/scroll.js#L15-L42) - [drag.js:26-111](file://fms-vue/src/components/ui/utils/drag.js#L26-L111) ## 详细组件分析 ### Modal 模态框 - 触发机制:v-model:open 受控或 defaultOpen 非受控;支持遮罩点击、Esc、关闭按钮、取消按钮关闭;confirmLoading 期间阻止关闭。 - 生命周期:打开时记录触发元素、锁定滚动、聚焦首项、绑定键盘事件、启用拖拽/缩放;关闭时恢复滚动、解绑事件、还原焦点。 - 状态控制:isOpen 计算属性统一受控/非受控;panelStyle 根据是否缩放决定宽高;isFullscreen 控制全屏。 - 可访问性:role=dialog、aria-modal、aria-labelledby 关联标题,关闭按钮 aria-label。 - 交互增强:标题栏拖拽移动、四边缩放手柄、全屏切换。 ```mermaid flowchart TD Start(["打开"]) --> Lock["锁定滚动"] Lock --> Focus["聚焦首项"] Focus --> Bind["绑定键盘/拖拽/缩放"] Bind --> Wait{"等待关闭"} Wait --> |Esc/遮罩/取消| Close["请求关闭"] Close --> Unlock["解锁滚动"] Unlock --> Restore["还原焦点"] Restore --> End(["结束"]) ``` 图表来源 - [modal.vue:118-198](file://fms-vue/src/components/ui/modal/modal.vue#L118-L198) - [modal.vue:201-285](file://fms-vue/src/components/ui/modal/modal.vue#L201-L285) 章节来源 - [modal.vue:1-291](file://fms-vue/src/components/ui/modal/modal.vue#L1-L291) - [focus.js:22-63](file://fms-vue/src/components/ui/utils/focus.js#L22-L63) - [scroll.js:15-42](file://fms-vue/src/components/ui/utils/scroll.js#L15-L42) - [drag.js:26-111](file://fms-vue/src/components/ui/utils/drag.js#L26-L111) ### Drawer 抽屉 - 触发机制:placement 控制滑出方向(left/right/top/bottom),默认右侧;支持遮罩点击、Esc、关闭按钮关闭。 - 生命周期:打开时锁定滚动、聚焦首项、绑定键盘事件、启用边缘缩放;关闭时恢复滚动、解绑事件、还原焦点。 - 状态控制:isOpen 计算属性统一受控/非受控;panelStyle 根据方向与是否缩放决定 width/height。 - 可访问性:role=dialog、aria-modal、aria-labelledby 关联标题,关闭按钮 aria-label。 ```mermaid classDiagram class Drawer { +open +defaultOpen +title +placement +width +height +mask +maskClosable +keyboard +closable +resizable +setOpen(value) +requestClose() } Drawer --> Focus : "focusFirst/trapFocus" Drawer --> Scroll : "lock/unlock" Drawer --> Drag : "bindResize" ``` 图表来源 - [drawer.vue:1-219](file://fms-vue/src/components/ui/drawer/drawer.vue#L1-L219) - [focus.js:22-63](file://fms-vue/src/components/ui/utils/focus.js#L22-L63) - [scroll.js:15-42](file://fms-vue/src/components/ui/utils/scroll.js#L15-L42) - [drag.js:130-202](file://fms-vue/src/components/ui/utils/drag.js#L130-L202) 章节来源 - [drawer.vue:1-219](file://fms-vue/src/components/ui/drawer/drawer.vue#L1-L219) - [focus.js:22-63](file://fms-vue/src/components/ui/utils/focus.js#L22-L63) - [scroll.js:15-42](file://fms-vue/src/components/ui/utils/scroll.js#L15-L42) - [drag.js:130-202](file://fms-vue/src/components/ui/utils/drag.js#L130-L202) ### Message 消息提示 - 触发机制:通常由命令式 API 调用(见计划文档),组件本身支持 props 配置 type/content/duration/closable。 - 生命周期:mounted 启动自动关闭计时器,beforeUnmount 清理计时器;close 事件交由容器移除条目。 - 状态控制:duration<=0 不自动关闭;closable 显示关闭按钮。 - 可访问性:role="alert",图标与文案清晰表达类型。 ```mermaid sequenceDiagram participant C as "容器" participant M as "Message" C->>M : 渲染条目(id,type,content,duration,closable) M->>M : onMounted -> startTimer() alt 自动关闭 M-->>C : emit('close') after duration else 手动关闭 C->>M : 点击关闭 M-->>C : emit('close') end ``` 图表来源 - [message.vue:1-77](file://fms-vue/src/components/ui/message/message.vue#L1-L77) 章节来源 - [message.vue:1-77](file://fms-vue/src/components/ui/message/message.vue#L1-L77) ### Notification 通知 - 触发机制:命令式 open(options),组件支持 title/description/duration/closable。 - 生命周期:同 Message,自动关闭计时器在 mounted 启动、卸载时清理。 - 状态控制:duration<=0 不自动关闭;closable 显示关闭按钮。 - 可访问性:role="alert",标题与描述层次清晰。 ```mermaid sequenceDiagram participant C as "容器" participant N as "Notification" C->>N : 渲染条目(id,type,title,description,duration,closable) N->>N : onMounted -> startTimer() alt 自动关闭 N-->>C : emit('close') after duration else 手动关闭 C->>N : 点击关闭 N-->>C : emit('close') end ``` 图表来源 - [notification.vue:1-82](file://fms-vue/src/components/ui/notification/notification.vue#L1-L82) 章节来源 - [notification.vue:1-82](file://fms-vue/src/components/ui/notification/notification.vue#L1-L82) ### Popover 气泡卡片 - 触发机制:trigger 支持 click/hover/focus/contextmenu/manual;hover 时通过延迟定时器避免误触。 - 定位算法:computePosition 基于期望 placement,计算初始坐标,溢出时 flip 翻转主轴方向,再 shift 约束回视口内;支持箭头与跟随触发器宽度。 - 生命周期:打开时注册全局 pointerdown/keydown/resize/scroll 监听,nextTick 后计算位置并启动 ResizeObserver;关闭时清理所有监听与定时器。 - 可访问性:hover 触发时 contentRole 为 tooltip,其余为 dialog。 ```mermaid flowchart TD A["打开"] --> B["nextTick 计算位置"] B --> C{"溢出?"} C -- 是 --> D["flip 翻转主轴"] D --> E{"仍溢出?"} E -- 是 --> F["shift 约束回视口"] E -- 否 --> G["应用位置"] C -- 否 --> G G --> H["监听 resize/scroll 更新"] ``` 图表来源 - [popover.vue:94-148](file://fms-vue/src/components/ui/popover/popover.vue#L94-L148) - [position.js:76-99](file://fms-vue/src/components/ui/utils/position.js#L76-L99) 章节来源 - [popover.vue:1-294](file://fms-vue/src/components/ui/popover/popover.vue#L1-L294) - [position.js:1-99](file://fms-vue/src/components/ui/utils/position.js#L1-L99) ### Tooltip 工具提示 - 触发机制:固定 trigger=hover,其他行为(定位、Teleport、延迟、Esc 关闭)由 Popover 提供。 - 内容优先级:content 纯文本与 #content 插槽同时存在时,插槽优先。 章节来源 - [tooltip.vue:1-59](file://fms-vue/src/components/ui/tooltip/tooltip.vue#L1-L59) - [popover.vue:1-294](file://fms-vue/src/components/ui/popover/popover.vue#L1-L294) ## 依赖关系分析 - Modal/Drawer 依赖 focus.js(焦点陷阱)、scroll.js(滚动锁)、drag.js(拖拽/缩放)。 - Popover/Tooltip 依赖 position.js(定位算法)。 - Message/Notification 作为单条渲染单元,被各自管理器(计划文档中定义)在 body 上以单例容器管理,使用 TransitionGroup 完成堆叠与过渡。 ```mermaid graph LR Modal --> Focus Modal --> Scroll Modal --> Drag Drawer --> Focus Drawer --> Scroll Drawer --> Drag Popover --> Position Tooltip --> Popover Message --> |被容器管理| Message Notification --> |被容器管理| Notification ``` 图表来源 - [modal.vue:1-291](file://fms-vue/src/components/ui/modal/modal.vue#L1-L291) - [drawer.vue:1-219](file://fms-vue/src/components/ui/drawer/drawer.vue#L1-L219) - [popover.vue:1-294](file://fms-vue/src/components/ui/popover/popover.vue#L1-L294) - [tooltip.vue:1-59](file://fms-vue/src/components/ui/tooltip/tooltip.vue#L1-L59) - [focus.js:1-63](file://fms-vue/src/components/ui/utils/focus.js#L1-L63) - [scroll.js:1-42](file://fms-vue/src/components/ui/utils/scroll.js#L1-L42) - [drag.js:1-202](file://fms-vue/src/components/ui/utils/drag.js#L1-L202) - [position.js:1-99](file://fms-vue/src/components/ui/utils/position.js#L1-L99) 章节来源 - [modal.vue:1-291](file://fms-vue/src/components/ui/modal/modal.vue#L1-L291) - [drawer.vue:1-219](file://fms-vue/src/components/ui/drawer/drawer.vue#L1-L219) - [popover.vue:1-294](file://fms-vue/src/components/ui/popover/popover.vue#L1-L294) - [tooltip.vue:1-59](file://fms-vue/src/components/ui/tooltip/tooltip.vue#L1-L59) - [focus.js:1-63](file://fms-vue/src/components/ui/utils/focus.js#L1-L63) - [scroll.js:1-42](file://fms-vue/src/components/ui/utils/scroll.js#L1-L42) - [drag.js:1-202](file://fms-vue/src/components/ui/utils/drag.js#L1-L202) - [position.js:1-99](file://fms-vue/src/components/ui/utils/position.js#L1-L99) ## 性能与可访问性 - 性能 - Modal/Drawer:仅在打开期间注册全局键盘事件与拖拽/缩放监听,关闭/卸载时彻底清理;Transition 仅使用 opacity/transform 等低成本属性。 - Popover:使用 requestAnimationFrame 合并高频滚动/缩放更新;ResizeObserver 观察浮层尺寸变化;hover 触发使用延迟定时器减少抖动。 - Message/Notification:单例容器,条目数量受控;计时器在卸载时清理,避免内存泄漏。 - 可访问性 - Modal/Drawer:role=dialog、aria-modal、aria-labelledby 关联标题,关闭按钮具备 aria-label;焦点陷阱确保 Tab 循环。 - Popover:hover 触发时 contentRole 为 tooltip,其余为 dialog;箭头与定位保证信息可读。 - Message/Notification:role="alert",明确语义。 章节来源 - [modal.vue:118-198](file://fms-vue/src/components/ui/modal/modal.vue#L118-L198) - [drawer.vue:106-154](file://fms-vue/src/components/ui/drawer/drawer.vue#L106-L154) - [popover.vue:212-244](file://fms-vue/src/components/ui/popover/popover.vue#L212-L244) - [message.vue:34-56](file://fms-vue/src/components/ui/message/message.vue#L34-L56) - [notification.vue:36-58](file://fms-vue/src/components/ui/notification/notification.vue#L36-L58) ## 异步操作与错误处理 - 异步操作反馈 - Modal 确定按钮 loading 态(confirmLoading):在加载中禁止重复提交与关闭,避免打断异步流程。 - Message/Notification:自动关闭时长可通过 duration 配置;0 表示不自动关闭,适合需要用户确认的错误提示。 - 错误处理 - 全局键盘事件与拖拽/缩放监听均在打开时注册、关闭/卸载时清理,防止残留导致异常。 - 滚动锁计数式管理,支持多层弹窗嵌套,最后一个关闭才恢复滚动,避免样式错乱。 - 计时器在 beforeUnmount 清理,避免组件销毁后回调引发异常。 章节来源 - [modal.vue:90-104](file://fms-vue/src/components/ui/modal/modal.vue#L90-L104) - [modal.vue:128-198](file://fms-vue/src/components/ui/modal/modal.vue#L128-L198) - [drawer.vue:91-154](file://fms-vue/src/components/ui/drawer/drawer.vue#L91-L154) - [message.vue:34-56](file://fms-vue/src/components/ui/message/message.vue#L34-L56) - [notification.vue:36-58](file://fms-vue/src/components/ui/notification/notification.vue#L36-L58) - [scroll.js:15-42](file://fms-vue/src/components/ui/utils/scroll.js#L15-L42) ## 动画、定位与多语言 - 动画效果 - Modal:遮罩淡入/淡出,面板缩放进入/退出;支持 prefers-reduced-motion 降级。 - Drawer:遮罩淡入/淡出,面板按方向平移滑入/滑出。 - Popover/Tooltip:使用 Transition 名称与 CSS 变量控制过渡。 - Message/Notification:单条过渡与 TransitionGroup 重排,实现平滑堆叠与退场。 - 定位算法 - Popover 使用 computePosition:先按期望 placement 计算坐标,溢出则 flip 翻转主轴方向,再 shift 约束回视口内;支持 offset 与箭头吸附。 - 多语言支持 - 组件文案(如 Modal 的 okText/cancelText、Drawer 的关闭按钮标签)通过 props 或插槽注入,便于接入 i18n 系统;计划文档指出复用 lucide 图标与主题变量,未硬编码颜色与文案。 章节来源 - [modal.vue:201-285](file://fms-vue/src/components/ui/modal/modal.vue#L201-L285) - [drawer.vue:157-213](file://fms-vue/src/components/ui/drawer/drawer.vue#L157-L213) - [popover.vue:247-289](file://fms-vue/src/components/ui/popover/popover.vue#L247-L289) - [tooltip.vue:27-54](file://fms-vue/src/components/ui/tooltip/tooltip.vue#L27-L54) - [position.js:76-99](file://fms-vue/src/components/ui/utils/position.js#L76-L99) ## 故障排查 - 焦点丢失或无法关闭 - 检查是否在打开时成功执行 focusFirst 与 trapFocus;确认 panelRef 已渲染且可聚焦。 - 确认 Esc 键监听已绑定且未被 preventDefault 拦截。 - 滚动异常 - 检查 lockScroll/unlockScroll 是否成对调用;多个弹窗嵌套时确保计数归零才恢复滚动。 - 浮层遮挡或错位 - 检查 Popover 的 placement、offset 与 followTriggerWidth;确认 computePosition 返回值与 floatStyle 应用正确。 - 计时器未清理 - 确认 message/notification 的 beforeUnmount 清理了 setTimeout;避免组件销毁后继续触发 close。 章节来源 - [modal.vue:118-198](file://fms-vue/src/components/ui/modal/modal.vue#L118-L198) - [drawer.vue:106-154](file://fms-vue/src/components/ui/drawer/drawer.vue#L106-L154) - [popover.vue:212-244](file://fms-vue/src/components/ui/popover/popover.vue#L212-L244) - [message.vue:34-56](file://fms-vue/src/components/ui/message/message.vue#L34-L56) - [notification.vue:36-58](file://fms-vue/src/components/ui/notification/notification.vue#L36-L58) - [scroll.js:15-42](file://fms-vue/src/components/ui/utils/scroll.js#L15-L42) ## 结论 FMS 反馈组件体系围绕“可控、可访问、可组合”的原则构建:Modal/Drawer 提供强交互弹层能力,Message/Notification 提供轻量全局反馈,Popover/Tooltip 提供灵活的上下文提示。通过统一的焦点与滚动管理、健壮的定位算法与低开销动画,满足复杂业务场景下的用户体验需求。建议在实际项目中: - 根据反馈强度与上下文选择合适组件(强中断用 Modal,弱提示用 Message/Notification,上下文提示用 Popover/Tooltip)。 - 使用受控 open 状态管理生命周期,确保资源释放与状态一致。 - 借助 props 与插槽注入多语言文案,保持视觉与交互一致性。 - 在异步操作中合理使用 loading 与自动关闭策略,提升可用性。