
工作流自动化桌面应用AI 应用企业应用后端前端【免费下载链接】astron-rpaAgent-ready RPA suite with out-of-the-box automation tools. Built for individuals and enterprises.项目地址https://gitcode.com/bijinfeng/astron-rpa点击查看免费下载本篇技术指南以 frontend/docs/nice-modal.md 为核心骨架围绕 astron-rpa 前端组件库rpa/components内置的 Vue 版 NiceModal源自 eBay 开源项目ebay/nice-modal-react的 Vue 移植展开讲解如何把 Vue 组件形式的模态框Modal / Drawer / Dialog转换为基于 Promise 的声明式 API。读完本文你将掌握 Provider 包裹、create高阶组件、show/hide/register四种调用方式以及useModal组合式 API 的完整用法并理解其背后基于全局 store 与Promise.withResolvers的实现原理可直接在 astron-rpa 的 web-app 业务代码中落地使用。一、背景为什么要用 Promise 化的模态框管理传统 Vue 中打开一个模态框通常需要维护visible状态、监听确认/取消回调、处理参数传递与关闭后的结果回传代码分散且难以复用。vue-nice-modal 的核心理念是把「打开一个模态框」当作一次异步调用const res await NiceModal.show(MyModal, { title: 标题, content: 内容 })确认时通过resolve返回结果取消时通过reject抛出原因关闭流程自然融入try / catch业务逻辑更线性、可测试性更强。astron-rpa 将这一方案封装进前端组件库rpa/components的NiceModal目录供web-app等上层应用统一调用。二、安装与引入NiceModal 不是独立发布的 npm 包而是组件库rpa/components的组成部分。在 web-app 等消费方中通过包名直接引入import { NiceModal } from rpa/components从源码 frontend/packages/components/src/index.ts 可以看到组件库统一导出了NiceModal模块而 frontend/packages/components/src/components/NiceModal/index.ts 中NiceModal是一个聚合对象包含export const NiceModal { useModal, // Hook Provider, // 容器组件 create, // 高阶组件 hide, // 隐藏 register, // 注册 remove, // 移除 show, // 显示 unregister, // 注销 antdModal, // Ant Design Vue 适配器 antdDrawer, // Ant Design Vue Drawer 适配器 }下文所有 API 均来自该导出对象。三、第一步Provider 包裹应用模态框需要一个全局容器负责渲染因此要像 Vue Router 的router-view一样把NiceModal.Provider放在应用最外层!-- App.vue -- template NiceModalProvider router-view / /NiceModalProvider /template script setup import { NiceModal } from rpa/components const NiceModalProvider NiceModal.Provider /script从实现看Provider.ts 是一个渲染插槽加上内部NiceModalPlaceholder的组件setup返回的渲染函数是[slots.default?.(), h(NiceModalPlaceholder)]即在插槽内容之后追加一个「模态框占位渲染区」。占位区同文件NiceModalPlaceholder会读取全局 store 中所有可见的模态框 ID只渲染那些已在MODAL_REGISTRY中登记过的组件并透传id与注册时保存的默认propsreturn toRender.map(t h(t.comp, { key: t.id, id: t.id, ...t.props }))这也解释了为什么「声明式 ID 调用」能生效模态框真实挂载点统一由 Provider 管理调用方不需要关心组件放在哪里。四、第二步创建模态框组件4.1 编写普通 Vue 组件模态框本体就是一个普通 Vue 组件通过useModal拿到modal对象含visible、resolve、reject、hide、remove等能力并把visible绑定到具体 UI 库的显隐属性上。文档以 Vant 的van-dialog为例!-- my-modal.vue -- template van-dialog show-cancel-button :valuemodal.visible :close-on-click-overlayfalse :titletitle :messagecontent closedmodal.remove confirmhandleConfirm cancelhandleCancel / /template script setup import { NiceModal } from rpa/components const modal NiceModal.useModal() defineProps([title, content]) const handleCancel () { modal.reject(cancel) modal.hide() } const handleConfirm () { modal.resolve(confirm) modal.hide() } /script关键点modal.visible是只读计算属性驱动 UI 组件的显隐确认/取消时先resolve(value)/reject(reason)结束外层await再hide()关闭动画结束后通过closed或 antd 的afterClose调用modal.remove()从 DOM 中移除并清理回调。在组件内部调用NiceModal.useModal()且不传参时会通过inject(NICE_MODAL_ID_KEY)拿到由外层create高阶组件注入的模态框 ID详见 hooks.ts因此内部无需关心自己的 ID。4.2 适配 Ant Design Vueantdv文档说明「可与任何 UI 库配合使用如 antdv」并提供了两个现成适配器直接通过v-bind一次性绑定 props 即可!-- my-drawer.vue -- template a-drawer v-bindNiceModal.antdDrawer(modal)xxx/a-drawer /template!-- my-modal.vue -- a-modal v-bindNiceModal.antdModal(modal)xxx/a-modal两个适配器的实现位于 utils.tsexport function antdModal(modal) { return { open: modal.visible, // 受控显隐 onCancel: () modal.hide(), // 取消 - 隐藏 afterClose: () { modal.resolveHide() // 先 resolve 隐藏 Promise modal.remove() // 再移除保证关闭动画完整播放 }, } } export function antdDrawer(modal) { return { open: modal.visible, onClose: () modal.hide(), onAfterOpenChange: (v) { if (!v) { modal.resolveHide() modal.remove() } }, } }resolveHide的存在是为了让「关闭动画完成」也可以被 Promise 化等待避免在动画未结束时提前移除 DOM。4.3 用 create 高阶组件包装为了让模态框能以组件形式被声明、也能被show直接调用需要用NiceModal.create包一层// my-modal.js import { NiceModal } from rpa/components import _MyModal from ./my-modal.vue export const MyModal NiceModal.create(_MyModal)create的实现utils.ts会返回一个新的defineComponent它在挂载时把自身 ID 记入ALREADY_MOUNTED通过provide(NICE_MODAL_ID_KEY, id)把 ID 下发给内部组件并从全局 store 读取该 ID 的args合并进真实组件的 propsreturn () { if (!modalInfo.value) return null return h(Comp, { ...restProps, ...modalInfo.value?.args }) }也就是说show(MyModal, args)传入的参数最终会以 props 形式注入到原始组件的根元素上。五、第三步四种调用方式5.1 基础用法直接传组件async function showModal() { try { const res await NiceModal.show(MyModal, { title: 标题, content: 内容, }) console.log(结果:, res) } catch (error) { console.log(取消:, error) } }show返回 Promise组件内modal.resolve(value)时res拿到valuemodal.reject(reason)时进入catch分支error为reason。5.2 声明式用法通过 ID 引用已声明的模态框如果模态框已经在模板中通过MyModal idmy-modal /声明可继承声明处的上下文如 provide/inject 的依赖注入就可以直接用 ID 字符串调用template MyModal idmy-modal / /template script setup const showModal async () { try { const res await NiceModal.show(my-modal, { title: 标题, content: 内容, }) console.log(结果:, res) } catch (error) { console.log(取消:, error) } } /script此时create高阶组件在onMounted中会读取 store 中的delayVisible标记——如果show先于挂载发生则挂载后自动补开见 utils.ts 与 store.ts 中visible: !!ALREADY_MOUNTED[modalId]、delayVisible: !ALREADY_MOUNTED[modalId]的配合。5.3 Hook 用法useModal 组合式 API在业务组件里可以用useModal(MyModal)拿到一个自带show方法的句柄无需关心 ID 字符串const modal NiceModal.useModal(MyModal) async function showModal() { try { const res await modal.show({ title: 标题, content: 内容, }) console.log(结果:, res) } catch (error) { console.log(取消:, error) } }5.4 注册用法register 后按 ID 调用对于「全站通用、随处可开」的模态框如反馈、API Key 管理可以预先注册之后只用字符串 ID// 预先注册模态框 NiceModal.register(register-modal, MyModal) async function showModal() { try { const res await NiceModal.show(register-modal, { title: 标题, content: 内容, }) console.log(结果:, res) } catch (error) { console.log(取消:, error) } }注意register仅登记组件存入MODAL_REGISTRY并不会立即挂载到 DOM真正渲染仍然由 Provider 的占位组件统一完成。文档特别提到「可继承声明处上下文」的声明式用法与注册用法各有适用场景声明式适合需要依赖注入上下文的模态框注册式适合纯参数驱动的全局模态框。六、源码原理全局 store 与 Promise 回调要正确使用 NiceModal理解底层数据流很有帮助。它由三个核心机制构成全部位于 frontend/packages/components/src/components/NiceModal/ 目录1. 全局状态 storestore.ts使用vueuse/core的createGlobalState创建全局单例state以模态框 ID 为键保存{ id, args, visible, delayVisible }通过dispatch(action)处理nice-modal/show、nice-modal/hide、nice-modal/remove、nice-modal/set-flags四种 action分别对应置为可见、置为隐藏、从 store 删除、附加自定义标记。2. 注册表与挂载标记contants.tsMODAL_REGISTRYID - 组件与默认 props 的映射register/unregister操作的就是它ALREADY_MOUNTED记录哪些模态框已经真实挂载用于判断delayVisibleNICE_MODAL_ID_KEY一个Symbol(NiceModalId)用作跨组件传递 ID 的注入键modalCallbacks/hideModalCallbacks分别保存「打开结果 Promise」与「隐藏完成 Promise」的Promise.withResolvers句柄。3. 动作与 Hookutils.ts hooks.tsshow(modal, args)的调用链是getModalId解析 ID组件没有 ID 时自动生成_nice_modal_{n}并挂在组件的 Symbol 属性上→ 若传的是组件且未注册则自动register→dispatch({ type: nice-modal/show })写入 store → 创建Promise.withResolvers()存入modalCallbacks并返回其 promise。hide/remove同理分别 dispatch 对应 action 并清理回调。useModal(modal?, args?)返回的句柄hooks.ts本质上是对show/hide/remove的 ID 化封装并额外暴露resolve(value)/reject(reason)结束show返回的 Promise完成后即删除回调resolveHide(value)结束hide返回的 Promise供hide(modalId)等待关闭动画完成读写visible时自动触发show/hide因此也可以与表单的v-model式绑定互通。七、API 参考组件API说明NiceModal.Provider模态框容器组件需包裹在应用最外层内含自动渲染占位区高阶组件与静态方法API参数返回说明NiceModal.create(Component)普通 Vue 组件模态框高阶组件包装组件支持声明式Comp id与程序化调用NiceModal.show(modalId, args?)模态框 ID 或组件可选参数Promise显示模态框resolve成功 /reject失败NiceModal.hide(modalId)模态框 ID 或组件Promise隐藏模态框可等待关闭动画完成NiceModal.remove(modalId)模态框 ID 或组件无从 DOM 与 store 中移除模态框NiceModal.register(id, component, props?)ID、组件、默认 props无注册模态框组件NiceModal.unregister(id)ID无注销模态框组件NiceModal.antdModal(modal)useModal返回值绑定对象antdva-modal适配器open/onCancel/afterCloseNiceModal.antdDrawer(modal)useModal返回值绑定对象antdva-drawer适配器open/onClose/onAfterOpenChangeHookuseModal(modal?, args?)返回值包含成员类型/行为说明idstring模态框 IDargs计算属性模态框当前参数visible可读写可见状态赋值时自动show/hideshow(args?)方法显示模态框hide()方法隐藏模态框remove()方法移除模态框resolve(value)方法解析打开 Promise成功路径reject(reason)方法拒绝打开 Promise失败/取消路径resolveHide(value)方法解析隐藏 Promise八、项目中的真实应用案例astron-rpa 的 web-app 已在多个业务模块中采用这套方案以下路径可直接对照学习组件包装导出RobotSelectModal/index.ts 中export const RobotSelectModal NiceModal.create(_RobotSelectModal)是「创建后由其他模块show」的标准写法。模块级句柄复用PythonPackageManagement/modals.ts 在模块顶层导出useModal(create(...))得到的句柄把「安装中」模态框的打开能力收敛到单一文件供多个组件与 hook 共享。事件式打开HeaderControl.vue 中NiceModal.show(SettingCenterModal)不带参数打开设置中心ApiKeyManage.vue 中NiceModal.show(NewApiModal, {...})则携带新建 API Key 的默认参数。与 Promise 链结合插件安装场景中可以看到NiceModal.show(PluginUpdateModal, {}).then(async () { ... })的写法见 useBrowerPlugin.ts即把模态框关闭当作异步流程中的一个环节继续编排后续逻辑。九、最佳实践小结Provider 只放一次放在应用根组件如 App.vue与router-view平级确保所有路由页面内的模态框都能被渲染。组件内部只用useModal()在模态框组件内不要传 ID让create高阶组件通过 Symbol 注入自动完成绑定只有「调用方」才需要show(Comp)/useModal(Comp)/show(id)。确认/取消必须成对resolve与reject都要调用否则调用方的await会一直挂起关闭动画类 UI如 antdv记得在afterClose/onAfterOpenChange(false)中先resolveHide再remove。按场景选择调用方式纯参数驱动的全局弹窗用register ID需要继承 provide/inject 上下文表单、复杂交互用声明式Comp id组件内部需要多次复用的用useModal(Comp)句柄。统一从rpa/components导入不要直接引用组件库内部目录NiceModal的全部公开能力都聚合在组件库的index.ts导出中与项目其他基础组件如 Sheet、Splitter、CodeEditor保持一致的引用习惯。输出文章赞分享工作流自动化桌面应用AI 应用企业应用后端前端【免费下载链接】astron-rpaAgent-ready RPA suite with out-of-the-box automation tools. Built for individuals and enterprises.项目地址https://gitcode.com/bijinfeng/astron-rpa点击查看免费下载相关推荐astron-rpa 前端国际化i18n实战基于 lobehub/i18n-cli 与 i18next 的中英双语方案astron rpa 前端国际化i18n实战基于 lobehub/i18n cli 与 i18next 的中英双语方案 本文是 astron rpa星工作流自动化桌面应用AI 应用企业应用后端前端AstronRPA 前端平台实战指南基于 pnpm Workspaces 的 Vue 3 Electron 多端 RPA 前端单体仓库AstronRPA 前端平台实战指南基于 pnpm Workspaces 的 Vue 3 Electron 多端 RPA 前端单体仓库 AstronRPA工作流自动化桌面应用AI 应用企业应用后端前端astron-agent 前端 ButtonGroup 与 SpaceButton基于权限控制的按钮组组件实战指南astron agent 前端 ButtonGroup 与 SpaceButton基于权限控制的按钮组组件实战指南 ButtonGroup 与 SpaceBu人工智能AI AgentAgent 编排RPA后端前端企业应用上一篇5分钟终极指南用VisualCppRedist AIO一键修复Windows运行库缺失问题下一篇FanControlWindows平台风扇控制终极指南打造个性化散热管理系统创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考