ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

WeKan 归档与删除:从 UI 操作路径到 archived 字段与级联恢复的源码实现

WeKan 归档与删除:从 UI 操作路径到 archived 字段与级联恢复的源码实现 WeKan 归档与删除从 UI 操作路径到 archived 字段与级联恢复的源码实现【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan本篇围绕 WeKan 官方文档 Archive-and-Delete.md 展开讲清楚看板Board、泳道Swimlane、列表List、卡片Card四级对象移入归档、恢复、最终删除的完整操作路径并结合 models/boards.js、models/cards.js、models/swimlanes.js 与 client/components/sidebar/sidebarArchives.js 等源码说明归档背后archived/archivedAt标志位、级联归档/恢复机制以及删除不可撤销的实现边界。读完后你既能按文档路径在界面上完成归档与删除也能从源码层面理解哪些操作可恢复、哪些是 FINAL DELETE。一、功能适用前提归档与删除功能位于Standalone WeKan上即 Snap / Docker / Source / VirtualBox 四种独立部署形态其中部分功能同样存在于 Sandstorm WeKan如从归档中恢复看板时客户端会额外做 Sandstorm 环境检测见 client/components/boards/boardArchive.js。需要明确区分两个概念归档Archive对象从正常视图中隐藏但数据仍完整保留可随时恢复删除Delete文档明确强调 NO UNDO、Its FINAL DELETE——删除没有任何撤销机制执行后数据被物理移除。二、移入归档四级对象均可恢复文档列出的四条归档入口均通过菜单 ☰ 触发对象操作路径看板Board ☰ Move Board to Archive泳道Swimlane ☰ Move Swimlane to Archive列表List ☰ Move List to Archive卡片Card ☰ Move Card to Archive界面上该菜单项对应的 i18n 文案为archiveMove to Archive见 imports/i18n/data/en.i18n.json 第 188 行。2.1 归档在数据层做了什么从源码看归档本质上是给文档打上archived: true与archivedAt时间戳两个字段。以最简单的看板为例models/boards.jsasync archive() { return await Boards.updateAsync(this._id, { $set: { archived: true, archivedAt: new Date() } }); }, async restore() { return await Boards.updateAsync(this._id, { $set: { archived: false } }); },archivedAt字段在界面上有直接用途看板归档页按archivedAt倒序展示归档时间client/components/boards/boardArchive.jade 中渲染displayDate archivedAt LLL。值得注意的是恢复时只把archived置回false并不清除archivedAt因此 client/components/boards/boardsList.js 的注释说明在archivedAt字段被引入之前归档的看板没有该值界面用—占位。2.2 卡片的级联归档子卡片跟随父卡片卡片的archive()不是简单改一个标志位而是先遍历所有子卡片parentId指向自己的卡片逐个归档再归档自己models/cards.jsasync archive() { await this.applyToChildren(async card { await card.archive(); }); return Cards.updateAsync(this._id, { $set: { archived: true, archivedAt: new Date() }, }); },restore()对称地先恢复子卡片再恢复父卡片。这保证了子卡片subtask不会在父卡片归档后仍以活跃状态残留在板面上。2.3 泳道的级联归档卡片必须随泳道一起归档泳道的归档逻辑最为复杂models/swimlanes.js。源码注释解释了为什么必须级联对应 issue #2292若泳道归档时其下的卡片仍保持archived: false这些卡片在所有板视图中不可见、在归档侧边栏中也不可见还会被启动时的数据修复逻辑server/lib/schemaUpgradeSteps.js中针对 #1959/#1971 的 rescue重新分配到别的泳道——之后再恢复该泳道时会发现它是空的实现方式先捕获一个archivedAt时间戳用于泳道本身然后对应随泳道归档的卡片由 models/lib/swimlaneArchive.js 的cardsToArchiveWithSwimlane判定逐个调用card.archive()最后再把泳道自身的archivedAt置为捕获值。恢复时同样讲究restore()只恢复那些archivedAt在泳道archivedAt之后的卡片cardsToRestoreWithSwimlane。也就是说用户在归档泳道之前就已经单独归档的卡片在泳道恢复后依然保持归档状态它们的archivedAt不会被覆盖。列表的归档也有一处级联模板列表归档时会先归档其中所有卡片models/lists.js。三、从归档中恢复或最终删除卡片/列表/泳道文档给出的恢复/删除入口是Board ☰ Archive Cards/Lists/Swimlanes Restore (or Delete - but that has no undo!! Its FINAL DELETE)该入口对应看板侧边栏的归档面板archivesSidebar模板由 client/components/sidebar/sidebarArchives.js 实现提供 Cards / Lists / Swimlanes 三个标签页分别展示当前看板下archived: true的卡片、列表与泳道统一按archivedAt: -1, modifiedAt: -1排序。3.1 归档侧边栏的分页加载机制归档数据不是全量推送到客户端的而是按需分页订阅每页大小ARCHIVE_PAGE_SIZE 30滚动容器接近底部阈值ARCHIVE_SCROLL_THRESHOLD_PX 120像素时以 200ms 节流触发加载更多client/components/sidebar/sidebarArchives.js订阅名为archiveSidebar服务端发布实现见 server/publications/cards.jsMeteor.publish(archiveSidebar, async function(boardId, activeTab, cardsLimit, listsLimit, swimlanesLimit))三个标签的 limit 分别独立传入客户端同时维护isArchiveReady/isLoadingMore状态避免重复请求。3.2 恢复时的边界处理原位置已不存在源码中处理了两个文档未展开的关键边界情况卡片恢复若卡片原所在列表已不存在会弹出restoreArchivedCardToList弹窗列出该看板所有未归档列表供选择选定后先moveOptionalArgs变更归属再restoreclient/components/sidebar/sidebarArchives.js列表恢复若列表原属泳道已不存在则弹出restoreArchivedListToSwimlane弹窗让用户重选泳道更新swimlaneId后再restore同文件 L464-L544打开已归档卡片点击归档侧边栏中的卡片会复用普通卡片的完整详情弹窗Popup.open(cardDetails)而不是受限的迷你预览且详情弹窗内也带有恢复操作源码注释对应 issue #1504每个标签还提供全部恢复js-restore-all-*与全部删除js-delete-all-*的批量操作删除同样带Popup.afterConfirm二次确认弹窗cardDelete/listDelete/swimlaneDelete。3.3 归档中的删除是 FINAL DELETE侧边栏中的删除按钮走的是物理移除例如卡片删除直接Cards.removeAsync(cardId)client/components/sidebar/sidebarArchives.js列表/泳道删除调用模型上的remove()。这与归档的$set: { archived: true }形成鲜明对比归档可逆删除不可逆。四、看板的恢复或最终删除All Boards Archive文档路径为All Boards Archive Restore (or Delete - but that has no undo!! Its FINAL DELETE)所有看板页面中的 Archive 页archivedBoards模板采用服务端分页订阅archivedBoards发布时只下发当前页的数据页数由getArchivedBoardsCount方法返回的总数计算client/components/boards/boardArchive.js。服务端实现有两处值得注意server/publications/boards.js发布函数Meteor.publish(archivedBoards, async function(searchTerm , limit 30, skip 0))支持按标题搜索并对searchTerm/limit/skip做了check()参数校验字段投影只包含看板瓦片渲染所需的_id、title、archived、archivedAt、color、type、description、permission、members、stars等——源码注释明确指出缺字段会让归档页渲染出灰色、无名的瓦片getArchivedBoardsCount方法的注释记录了一个真实缺陷修复顺序问题必须先check()参数、后判断登录态否则未登录调用方会触发 Meteor 的 Did not check() all arguments 异常而非返回 0相关测试见 tests/methodArgumentChecks.test.cjs、tests/archiveSection.test.cjs。恢复操作js-restore-board点击后调用看板的board.restore()并跳转进该看板在 Sandstorm 环境中有一个特殊分支若当前正处于该看板会话内恢复后会先再次archive()配合 Sandstorm 每个 pkg 单看板的约束见 client/components/boards/boardArchive.js。五、直接删除无撤销的完整路径文档 Deleting - NO UNDO 一节列出的四条删除路径与上述机制的关系如下删除对象操作路径实现看板All Boards Archive Delete归档页内删除物理移除卡片Card ☰ More Delete右下角菜单直接删除不经归档列表List ☰ More Delete右下角菜单直接删除不经归档泳道1) Swimlane ☰ Move Swimlane to Archive2) Board ☰ Archive 在归档侧边栏 Delete泳道没有菜单直删入口必须先归档再在归档面板删除最后一行值得特别留意泳道的唯一删除途径是先归档、再到归档侧边栏删除两步这与侧边栏中js-delete-swimlane走swimlane.remove()的实现一致client/components/sidebar/sidebarArchives.js。六、小结什么可恢复、什么不可恢复操作数据层行为可恢复性归档看板/泳道/列表/卡片$set: { archived: true, archivedAt: new Date() }卡片/泳道带级联可从归档页恢复恢复归档对象$set: { archived: false }卡片/泳道按archivedAt时间戳判定级联恢复范围—从归档页删除Restore 旁边的 DeleteremoveAsync/remove()物理移除不可恢复FINAL DELETE卡片/列表菜单 More Delete同上物理移除不可恢复从源码结构看WeKan 通过archived软标志 archivedAt时间戳把归档做成低成本、可精确还原的操作级联恢复能区分随容器归档的与用户单独归档的而所有带 Delete 字样的入口都直接执行物理删除并配二次确认弹窗两者边界清晰。这一设计也解释了为什么文档反复强调 Delete has no undo——它不是隐藏而是移除。延伸阅读归档看板页的整体设计见 docs/Features/Page/All-Boards.md归档相关回归测试可参考 tests/archiveSection.test.cjs、tests/archivePage.test.cjs、tests/archivedBoardPermanentDelete.test.cjs。【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进