EcoPaste 前端开发指南:基于 Tauri + React 的剪贴板管理工具前端架构与实践规范
桌面应用开发工具【免费下载链接】EcoPaste跨平台的剪贴板管理工具 | Cross-platform clipboard management tool项目地址https://gitcode.com/gh_mirrors/ec/EcoPaste点击查看免费下载EcoPaste 是一款跨平台剪贴板管理工具其前端采用 Tauri React 技术栈负责渲染 UI 与普通交互业务数据、持久化、操作系统行为与跨窗口生命周期状态全部交由 Rust 侧处理。本文以仓库.trellis/spec/frontend/下的前端开发规范为核心系统讲解前端目录结构、组件写法、Hooks 使用、Valtio 状态管理、类型安全与质量校验等完整工程实践帮助你快速理解 EcoPaste 前端的边界划分并掌握在类似 Tauri 桌面应用中落地Rust 拥有数据、React 只做渲染架构的具体方法。分层规则Rust 拥有数据React 只渲染结果.trellis/spec/frontend/index.md开篇即明确了前端在整个应用中的定位前端渲染 EcoPaste 的 UI 与普通交互调用 Rust 获取业务数据、持久化、操作系统行为和跨窗口生命周期状态。其中Layer Rule分层规则是最核心的约束React 只负责渲染命令command返回的结果、并把用户意图发送回 Rust。不要在 React 侧复制数据库状态、内容分类、FTS/全文搜索语义、窗口定位、快捷键注册、托盘行为、自启动、存储持久化等逻辑。这意味着剪贴板历史、分组、应用列表、备份、设置持久化、存储占用等全部归 Rust 后端所有对应src-tauri/src/下的db/、clipboard/、backup/、settings/等模块前端只能通过 Tauri command 调用并渲染结果。规范同时给出了主要参考文件是理解前端的最佳入口关注点文件应用外壳与主题App.tsx、useAppTheme.ts命令包装边界commands/index.ts设置镜像settings.ts剪贴板 UI 状态clipboardView.ts虚拟化列表List.tsx偏好设置 SchemapreferenceSchema.ts用户 SVG 清洗ClipboardGroupIcon/index.tsx目录结构先按边界、再按页面组织顶层布局前端代码的目录组织原则是先按边界boundary、再按页面page共享组件和 Hooks 直接放在src/下页面专属 UI 放在所属页面目录下后端契约合同在src/types/和src/constants/中镜像。完整布局如下src/ commands/ # 唯一的 Tauri invoke 包装边界 components/ # 共享 UI 原语与可复用组件 constants/ # 镜像 Rust 的命令、事件、窗口、动作常量 hooks/ # 针对 Tauri 事件、主题、键盘、数据的可复用 React Hooks i18n/ # react-i18next 初始化 locales/ # zh-CN 与 en-US 的 JSON 命名空间 pages/ # 路由级功能Clipboard、Preference、Preview、ContextMenu router/ # React Router 定义 stores/ # Valtio UI 状态与设置/窗口镜像 styles/ # 全局 SCSS types/ # Rust 契约与 UI Schema 的 TypeScript 镜像 unocss/ # 本地 UnoCSS 预设 utils/ # 小工具cn、is、log、shortcut功能归属原则只有在该工作流内有意义的组件才放进页面目录剪贴板列表的零件位于src/pages/Clipboard/components/与src/pages/Clipboard/components/cards/剪贴板预览的测量、布局、缓存和 Hooks 位于src/pages/Preview/偏好设置的 Schema、控件、服务和搜索工具位于src/pages/Preference/。src/components/只放共享原语如ShortcutRecorder、AssetImage、Tooltip、KeyHint、ClipboardGroupIcon等。命令与契约文件src/commands/index.ts是唯一导入 Tauriinvoke的文件源码确认。新增 Rust command 只在此处增加一个包装函数业务模块统一从/commands导入包装函数禁止裸调invoke或直接引用TAURI_COMMAND常量。跨层字符串字面量集中管理命令名src/constants/commands.ts事件名src/constants/events.ts窗口标签src/constants/windows.ts条目动作与捕获类型src/constants/itemActions.ts、src/constants/captureKinds.ts当 Rust 新增返回字段时需同步更新src/types/中最近的 TypeScript 镜像src/types/clipboard.ts专门标注了哪些字段由后端增强backend enriched、应直接渲染。命名与导入规范源码导入使用/别名页面组件用 PascalCase.tsxHooks 以use开头工具模块用简短小写命名。除非已有本地惯例否则避免在页面子目录中新增 barrel 文件。组件规范形状、样式、安全与可访问性组件形状与事件回调组件统一使用FCProps并在函数体内解构props这是ClipboardCard、ClipboardGroupIcon、App的本地风格const ClipboardCard: FCClipboardCardProps (props) { const { item, isSelected, onPointerEnter } props; // ... };JSX 事件回调应在组件体内命名为具名函数单一用途处理器用动作名handleDragStart、handleContextMenu通用事件用handleXxx。新组件避免参数解构和隐式返回箭头回调。渲染后端拥有的模型组件应直接渲染命令返回的字段不要在 React 中重算后端逻辑ClipboardCard按item.kind分发并展示item.availableActionsFilesCard消费fileEntries与filesPreviewKind颜色预览使用已被 Rust 清洗的colorPreviewdisplayCreatedAt由 Rust 完成格式化来源应用图标和图片缩略图是 Rust 准备好的绝对路径。如果组件需要某个昂贵、敏感或由数据库/OS 状态派生的字段应该加到 Rust 响应模型中而不是在 React 里推导。样式Ant Design 组件 UnoCSS 工具类控件用 Ant Design v6 组件布局用 UnoCSS 工具类。颜色必须来自 Ant Design token通过src/unocss/presetAntdColors.ts暴露如text-ant-secondary、bg-ant-container、border-ant-border。条件类名统一用/utils/cnclassName{cn(rounded-2 border border-ant-border-secondary, { border-ant-primary bg-ant-blue-1: isSelected, })}不要手动拼接类名Wind4 数字间距可用时不要引入任意像素工具类。Ant Design Button 自定义图标当 Ant DesignButton接收自定义图标节点如 UnoCSSi图标、KeyHint、ClipboardGroupIcon时必须使用src/components/CustomIconButtonCustomIconButton icon{i aria-hiddentrue classNamei-lucide:refresh-cw /} onClick{handleRefresh} sizesmall typetext /纯文字按钮可以继续用Button非按钮布局图标如引导页 hero 图标、卡片徽标无需此包装。原因在于Ant Design v6.5 只重置.ant-btn-icon内的直接svg子元素不再给图标槽位提供内联 flex 居中的盒。CustomIconButton通过Button的classNames.icon语义槽恢复槽位样式不添加额外 DOM 包装也不做全局.ant-*覆盖。虚拟化滚动条把 OverlayScrollbars 接入react-virtuoso时使用src/components/VirtuosoScroller。该包装组件拥有 OverlayScrollbars 根元素并把其scrollerRef渲染属性结果直接传给Virtuoso让 Virtuoso 保留原生滚动视口以进行范围计算、程序化滚动和键盘导航。虚拟列表默认滚动条行为应使用 OverlayScrollbars 的scrollbars.autoHide: move静止时隐藏、指针在列表上移动时显示、指针离开或停止移动后再次隐藏。普通非虚拟滚动容器不要套用此组件仅在确有真实非虚拟调用点需要改造时才新增普通 scroll-area 组件避免把VirtuosoScroller拉伸到不兼容的滚动契约上。HTML / SVG 安全剪贴板 HTML 预览以纯文本渲染除非真实功能再次需要富 HTML DOM否则不要新增通用 HTML 渲染器。任何用户提供的 HTML 或 SVG 渲染进 DOM必须在渲染边界用 DOMPurify 清洗并将清洗器收窄到该功能禁止危险标签/属性。例如ClipboardGroupIcon在把自定义 SVG 图标变成 mask 图片前会先清洗源码。页面组件中禁止直接使用dangerouslySetInnerHTML未来确有需要时应新增一个包装 DOMPurify 的小型自有组件并在其旁边注释允许的标签/禁止的属性。原生菜单与拖拽ClipboardCard通过popupClipboardItemMenu使用 Rust 支持的原生右键菜单而非 Web 内菜单。保持这一模式是因为原生路由规避了已知的 Tauri/muda 生命周期问题且动作可用性由后端拥有。拖出drag-out通过startDragClipboardItem启动前端阻止浏览器默认拖拽行为由 Rust 接管平台拖拽行为。可访问性剪贴板卡片使用roleoption和aria-selected装饰性图标设置aria-hiddentrueAssetImage在知道来源应用名时接收有意义的alt文本Ant Design 控件优先使用内置组件及其disabled、checked、open、onClick等属性不自行重建控件语义。常见错误清单在 React 中重算 Rust 已返回的业务规则如 reveal 动作、敏感内容打码在没有两个真实调用点之前就新增共享组件用全局.ant-*选择器改 Ant Design 内部样式应优先用语义classNames/styles槽位只渲染单一语言的界面文案新增偏好action设置却缺title、description、controlLabel与settingVisual图标条目——偏好标签由 setting id 推导缺失语言键会在界面渲染出原始schema.settings.*文本在虚拟化列表中于悬停动画期间卸载已测量的卡片内容——应保持备用的 note/original 层常驻挂载并测量活动层避免 Virtuoso 记录瞬时零高度条目。Hook 规范异步初始化、事件订阅与数据获取异步初始化与清理异步搭建/拆除使用 ahooks 的useMount与useUnmountuseEffect仅用于同步 DOM/状态反应或用 stale 标志包裹异步工作。本地示例useTauriListen挂载时订阅、把 unlisten 函数存进 ref、卸载时退订useAppTheme挂载时初始化 Tauri 主题监听并在卸载时清理随后用useEffect切换light/dark类App用useMount在settingsReadyresolve 后通知 Rust WebView 已就绪。事件处理器与 Ref长期存活的订阅应通过 ref 调用最新处理器不要为了避免陈旧闭包而在每次渲染时重新订阅。useTauriListen维护handlerRef.current handleruseKeyboardEvent使用useLatest让 Windows 的keyboard://nav事件在订阅保持稳定的前提下调用最新处理器。复杂组件如Clipboard/List.tsx中用 refs 存放滚动位置、待重载状态、当前重载函数和预览关闭回调使事件处理器无需重新订阅即可做出决策。数据获取以后端命令为数据源。useClipboardItems包装useInfiniteScroll调用listClipboardItems透传 Rust 拥有的分页字段并信任 Rust 返回的total/hasMore。不要把命令结果集合存进 Valtio 当作第二个数据库。结果保持局部于 hooks/组件除非另一个 UI 表面需要小型派生镜像例如 Footer 使用的clipboardStatsState.total。Tauri 事件组件级事件订阅使用useTauriListen并在消费点附近标注 payload 类型Clipboard/List.tsx中的ClipboardUpdatedPayload、useKeyboardEvent中的键盘 payload、剪贴板预览 hooks 中的窗口可见性 payload。当事件可能在剪贴板窗口隐藏时到达需要决定是否延迟处理剪贴板列表在窗口隐藏或滚动离开顶部时会延迟重载以避免隐藏窗口的 IPC 抖动和滚动跳变。平台 Hook用/utils/is做平台/窗口判断。useKeyboardEvent是标准键盘抽象macOS 与可聚焦窗口使用浏览器键盘事件Windows 剪贴板窗口因不可聚焦接收 Rust 的keyboard://nav事件。组件不应直接检查 Tauri 窗口标签除非在构建可复用的平台 hook。Effects 清单新增 hook 或 effect 前自问副作用是同步的吗用useEffect。会订阅、分配或返回清理句柄吗用useMountuseUnmount或严格限定范围的useEffect清理。回调需要长生命周期监听器内的最新状态吗用 ref。是获取业务数据吗优先走命令包装器并保持结果局部化。状态管理Valtio 只存 UI 状态与镜像所有权边界Valtio 存放前端 UI 状态和镜像不存放持久业务数据。当前 storessettingsStateRustSettingsStore的镜像仅通过settings://updated更新clipboardViewState剪贴板窗口的瞬态过滤条件clipboardStatsState当前过滤总数的小型共享显示值sourceAppsState与windowLifecycleState命令/事件结果的 UI 镜像。剪贴板历史、分组、应用、备份、设置持久化与存储占用全部保持后端所有。设置单向流settings.ts 明确记录了严格单向的数据流component - updateSettings(patch) - Rust SettingsStore persists - Rust emits settings://updated - every WebView listener Object.assigns settingsState不要在组件或服务中乐观修改settingsState。调用方如需立即的控制流可以使用updateSettings返回的快照但渲染必须等待事件镜像。源码中settingsState的初值只是占位字面量组件通过use(settingsReady)挂起待首屏快照灌入后才读取每个 webview 加载该模块一次事件订阅天然单例源码。剪贴板视图状态clipboardViewState只包含剪贴板窗口使用的查询/过滤状态category、keyword、groupId、range。分页保持在useClipboardItems内部不要把limit或offset加进 store。命名要谨慎List把 store 字段映射为ClipboardItemQuery所以与后端查询字段同名的新 UI 字段可能意外变成过滤器。store 注释特别点出pinned是一个已知的 footgun。服务端数据命令结果用命令 hooks 或局部组件状态承载useClipboardItems拥有当前虚拟化页面列表偏好面板读取当前设置快照并提交 patch备份/存储弹窗各自拉取自己的检查或占用状态。只有当多个距离较远的组件需要同一份小型 UI 镜像、且 Rust 仍为真相源时才把数据提升到 store。派生状态渲染期间优先从快照派生显示状态。useEffect仅在需要同步另一个 store 或外部系统时使用如从data.total更新clipboardStatsState.total。不要在 store 中缓存ClipboardItem数组的派生副本列表已通过数据 hook 处理延迟重载与变更。类型安全Rust 契约镜像与运行时校验边界Rust 契约镜像Rust serde 模型在 IPC 边界使用camelCase。TypeScript 镜像位于src/types/字段名必须与前端收到的完全一致。重要镜像clipboard.ts 镜像db::models::ClipboardItem、ClipboardAction、查询类型、分组、应用和分页结果settings.ts 镜像settings/model.rs其余命令专属响应类型若未被广泛共享就近放在src/commands/index.ts。新增 Rust 枚举或字段时要在同一改动里更新 TypeScript 联合类型/接口Rust 是封闭枚举时不要放宽成string。命令包装类型src/commands/index.ts应为每个包装函数的返回值与参数形状标注类型。唯一的通用callT包装统一处理任意 invoke 错误失败时log.error antd message error toast然后 rethrow调用方用try/catch决定成功后做什么不要再写错误 toast源码。调用方不得对命令结果做类型断言包装类型不足时应修复包装器或共享类型。设置补丁偏好控件在src/pages/Preference/services/preferenceSettings.ts中从 schema 路径构造SettingsPatch。数组整体替换与 Rustdeep_merge语义一致。复杂控件如可排序复选框树需同时保存选中值和完整顺序路径保证下次编辑会话时禁用项的排序不丢失。运行时校验边界外部或安全敏感的值由Rust 校验图片/图标文件名commands::clipboard::validate_image_file_nameCSS 颜色clipboard::sanitize_css_color设置 patch 形状与重复全局快捷键SettingsStore存储目标core::paths::validate_storage_target备份密码与容器头backup/mod.rs前端控件可以引导用户输入但不能作为持久数据、文件系统路径、CSS 注入或 OS 动作的唯一保护。常量与字面量命令名、事件、窗口、捕获类型、条目动作和 URL 使用集中常量。跨 Rust 与 TypeScript 的字符串要在两侧同步新增/更新常量。组件内不要写裸事件名或命令名TAURI_COMMAND只属于src/commands/index.ts组件导入包装函数。避免类型侵蚀命令 payload 不用any用unknown 收窄或定义 interfaceRust 使用skip_serializing_if时保持可选字段可选用判别联合如ClipboardKind、ClipboardSubKind驱动渲染保持ClipboardItemQuery与 Rust 默认值对齐可选字段用void 0省略不发送无关的哨兵值。质量保障lint、类型检查与 UI 验证常用校验命令前端改动使用以下检查pnpm lint pnpm tscpnpm lint运行 Biomepnpm tsc只做类型检查不输出。本仓库未配置前端测试运行器因此 UI 改动还需要对受影响路径做一次手动应用运行验证。涉及跨层改动时还需在src-tauri下运行 Rust 检查cd src-tauri cargo fmt cargo clippy -- -D warnings cargo test需要留意的 Biome 规则biome.json强制imports/classes/properties 排序、无未使用 import 或变量、无console.*、JSX 自闭合、严格清理未使用的模板字面量。noDangerouslySetInnerHtml被禁用是为了给窄范围、清洗器所有的逃生舱留口页面组件仍应避免直接注入原始 HTML。日志使用/utils/log而非 consoleimport 排序交给 Biome不要手工排序出另一套风格。必备前端模式只通过src/commands/index.ts的包装器调用 Tauri新代码用async/awaittry/catch避免.then()链匹配本地风格时用void 0表示刻意缺省的可选字段如Clipboard/List.tsxi18n 同时维护src/locales/zh-CN/与src/locales/en-US/当前窗口用getCurrentWebviewWindow()条件类名用cn用户提供的 HTML/SVG 在渲染边界用 DOMPurify 清洗。UI 验证要求UI 改动需手动验证主路径和至少一个边界情况剪贴板列表空状态、搜索、滚动分页、置顶/收藏条目、事件变更后的隐藏窗口更新行为预览文本、HTML/RTF、图片、文件、文件缺失、敏感内容打码偏好设置提交、重置、搜索、两种语言文件以及快捷键/托盘/自启动等相关副作用窗口改动受影响平台的主窗口、偏好窗口和预览窗口。如果改动影响 macOS NSPanel 时序或 Windows 不可聚焦键盘导航必须做手动桌面验证——类型检查覆盖不到这些路径。评审清单层属性和 Rust-first 边界一致吗命令/事件名是否集中并镜像新用户可见字符串是否存在于两种语言UI 渲染的是后端准备好的字段而非重算业务规则隐藏或休眠窗口是否需要延迟工作而非立即 IPC用的是 Ant Design token 颜色还是硬编码颜色是否避免了全局.ant-*覆盖除非不存在语义槽附发布版本管理的约束前端侧作为参考场景仓库还定义了发布版本管理的硬约束见 quality-guidelines.md版本触发pnpm release走稳定版release-itpnpm release-rc与pnpm release-beta分别追加--preReleaserc --preReleaseBase1与--preReleasebeta --preReleaseBase1release-it/bumper会把选定版本写入src-tauri/Cargo.toml的package.version与src-tauri/Cargo.lock的package.0.versionpackage.json.version是 Tauri 的版本源src-tauri/tauri.conf.json使用version: ../package.json发布 tag 必须为v${version}CHANGELOG.md由 release-it 生成作为 conventional-changelog 的infilerelease-it/conventional-changelog必须使用ignoreRecommendedBump: true避免 changelog 生成替代交互式版本选择release-it 不得发布到 npmTauri/GitHub release 工作交给 CI/release workflows验证方式干净工作树运行pnpm release --dry-run --ci --no-git.push检查 dry-run 输出包含预期的npm version、两份 Cargo 文件写入、release commit 信息与v${version}tag。这条约束展示了该仓库前端规范同样约束发布链路、版本元数据全链路对齐的严谨工程态度。总结EcoPaste 前端开发规范的核心可以概括为一句话React 渲染命令结果、发送用户意图Rust 拥有数据、持久化与系统行为。目录按边界优先组织、命令只在src/commands/index.ts收敛、Valtio 只存 UI 镜像、类型在src/types/镜像 Rust 契约、HTML/SVG 在渲染边界清洗——这套实践让一个多窗口、跨平台、涉及敏感剪贴板数据的桌面应用在前端保持轻薄与可维护。阅读本指南后你可以直接以 App.tsx、commands/index.ts、settings.ts、List.tsx 和 preferenceSchema.ts 为起点逐步深入 EcoPaste 的前端实现。赞分享桌面应用开发工具【免费下载链接】EcoPaste跨平台的剪贴板管理工具 | Cross-platform clipboard management tool项目地址https://gitcode.com/gh_mirrors/ec/EcoPaste点击查看免费下载相关推荐EcoPaste 工程架构与开发规范Rust-First 的 Tauri 跨平台剪贴板管理器实战指南EcoPaste 工程架构与开发规范Rust First 的 Tauri 跨平台剪贴板管理器实战指南 本文以 EcoPaste 仓库根目录的 AGENTS.m桌面应用EcoPaste 前端目录结构规范按边界分层的 React Tauri 工程化实践EcoPaste 前端目录结构规范按边界分层的 React Tauri 工程化实践 导读 本文以 EcoPaste 前端Tauri 2 React桌面应用开发工具EcoPaste 后端架构解析Tauri 组合根、模块边界与跨平台剪贴板管理实践EcoPaste 后端架构解析Tauri 组合根、模块边界与跨平台剪贴板管理实践 导读本文以 .trellis/spec/backend/architect桌面应用上一篇Databasus 根管理员账户演进从启动时种子化 admin 到“首个注册者即管理员”的设计与实践下一篇WorkshopDL5分钟免费下载Steam创意工坊模组的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考