WeKan 截止日期(Due Date)机制详解:颜色编码、倒计时与截止日变更追踪

WeKan 截止日期(Due Date)机制详解:颜色编码、倒计时与截止日变更追踪 WeKan 截止日期Due Date机制详解颜色编码、倒计时与截止日变更追踪【免费下载链接】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/wekanWeKan 为每张卡片提供了 received接收、start开始、due截止、end完成四类日期围绕其中最具管理价值的“截止日期”实现了一套由颜色编码、倒计时文案和变更次数追踪组成的可视化提醒体系。本文基于docs/Features/Date/Due-Date.md这一功能文档结合 WeKan 仓库中的颜色判定逻辑、卡片模板与数据模型源码完整讲解这套机制的设计意图、实现方式与验证手段。读完本文你将能够准确理解 WeKan 卡片截止日期的展示规则从何而来并在定制或排查 UI 问题时快速定位到对应源码。一、卡片日期体系四个日期各有其位功能文档首先给出了一张简洁的日期语义表它定义了 WeKan 卡片日期字段的四种用途received— bug 或任务被发现接收的时间start— 工作开始的时间due— 工作应当完成截止的时间end— 工作实际完成的时间。这四个日期在数据层都落在卡片或关联的板文档上。从 卡片模型 可以看到getDue()/setDue(dueAt)读写dueAt字段若当前卡片是“关联卡片”linked card或“关联板”linked board读写会透明地转发到真实卡片/板的文档上保证跨看板引用的日期始终一致unsetDue()通过$unset移除dueAt字段卡片上未设置截止日期时该字段为null。编辑入口方面卡片详情中的 due 区块在 cardDetails.jade 中按$eq field dueDate渲染已有截止日期时展示cardDueDate徽章组件未设置且当前用户可修改卡片时展示“”号添加按钮且受currentBoard.allowsDueDate板级开关控制并屏蔽 Worker 角色。点击后打开editCardDueDate弹层其初始化逻辑在 cardDate.jsTemplate.editCardDueDatePopup.onCreated(function () { const card Template.currentData(); setupDatePicker(this, { defaultTime: 1970-01-01 17:00:00, initialDate: card.getDue() ? card.getDue() : undefined, storeDate(date, currentCard) { return currentCard.setDue(date); }, deleteDate(currentCard) { return currentCard.unsetDue(); }, }); });其中storeDate/deleteDate分别对接上文提到的setDue/unsetDue完成了“弹层选择 → 模型写库”的完整链路。此外cardDate.js 中的formatCardDateForDisplay还会根据用户个人设置把日期显示为 Jalali波斯太阳历或按用户偏好的公历格式渲染——这属于纯显示层转换存储始终保持原生Date公历。二、截止日期颜色编码红 / 琥珀 / 灰的判定规则功能文档的核心段落描述了截止日期徽章的颜色语义红色red 已逾期overdue琥珀色amber 48 小时之内到期灰色grey 距今超过 48 小时才到期。且列表/迷你卡minicard与打开的卡片详情上显示的颜色永远一致。文档同时提到卡片已有 end 日期的情形源码把这一边界条件补充得更完整。颜色判定被抽成一个无 DOM 依赖的纯函数 dueDateClass被卡片详情徽章与迷你卡徽章共用这正是“两个视图颜色永远一致”这一承诺的实现基础export function dueDateClass(dueDate, now, endDate) { const due new Date(dueDate); const nowVal new Date(now); if (endDate) { const end new Date(endDate); if (end.getTime() due.getTime()) { return completed-early; // 提前完成 } return completed; // 按期或逾期完成 } const diffMs due.getTime() - nowVal.getTime(); const hoursDiff diffMs / (1000 * 60 * 60); if (hoursDiff 0) { return overdue; // 红色已逾期 } else if (hoursDiff 48) { return due-soon; // 琥珀色48 小时内到期 } return not-due; // 灰色48 小时以后到期 }完整的规则集可以整理为下表CSS 类名即徽章样式选择器条件返回类名视觉语义卡片已有 end 日期且 end 早于 duecompleted-early提前完成卡片已有 end 日期且 end 不早于 duecompleted已完成due 在当前时间之前overdue红色已逾期due 在 48 小时之内含恰好 48 小时due-soon琥珀色临近到期due 在 48 小时之后not-due灰色尚不紧急两处值得注意的实现细节48 小时边界是闭区间。hoursDiff 48意味着恰好 48 小时后的到期日仍算“琥珀色”。这一边界曾被专门做回归测试见下文测试小节对应 issue #6000“超过 48 小时约 1 分钟的将来日期必须显示为灰色”。now是作为参数传入的。徽章模板通过 dateNowTicker 订阅一个周期更新的“当前时间”变量cardDateOnCreated中subscribeDateNowTicker()因此一张长期停留的卡片徽章会在跨过 48 小时线时由灰变琥珀、在跨过到期时刻后变红无需刷新页面。调用侧见 cardDate.js 与 cardDate.jscardDueDate卡片详情和minicardDueDate迷你卡两个模板的classes()helper 均返回due-date ${dueDateClass(theDate, nowVal, endAt)}——同一个函数、同一份输入输出必然一致从结构上杜绝了两个视图颜色漂移的可能。三、倒计时文案“N 天后到期 / N 天前逾期”在颜色之外徽章还会在日期后追加一段相对倒计时文案例如 “Jun 15 (3 days left)” 或 “Jun 15 (Due today)”。这段文案由 dueCountdown 生成export function dueCountdown(dueDate, now) { const due new Date(dueDate); const nowVal new Date(now); const startOfDay date new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime(); const dayMs 24 * 60 * 60 * 1000; const diffDays Math.round((startOfDay(due) - startOfDay(nowVal)) / dayMs); if (diffDays 0) { return { key: due-today, days: 0 }; } else if (diffDays 0) { return { key: due-days-left, days: diffDays }; } return { key: due-days-overdue, days: -diffDays }; }关键设计点按“日历日”计算而非 24 小时差值先把两个日期都归零到当天 00:00 再相减。因此“今天稍后到期”的卡片显示 “Due today”而不是 “0 days left”。返回 i18n 键而不是文案本身days恒为非负数方向信息由keydue-today/due-days-left/due-days-overdue表达。调用点再用TAPi18n.__(key, { count: days })查翻译见 cardDate.js 中的dueCountdownText。这样dueDateColor.js保持零国际化依赖、易于单测。与颜色共用同一模块源码注释明确说明倒计时与徽章颜色“always agree”总是相互一致即dueCountdown与dueDateClass放在同一文件中共同维护。当卡片已有 end 日期时showDate/showTitlehelper 会省略倒计时后缀、只展示 end 日期本身cardDate.js避免“已完成却显示 N 天前逾期”的误导性文案。四、截止日期变更次数来自活动历史的问责视图功能文档的最后一节描述了“due date changed N times”截止日期已修改 N 次计数它显示在卡片详情的截止日期旁数据来源于卡片的活动历史activity history目的是让“截止日被改过多少次”可见便于团队对自己承诺的日期保持问责。实现上由三部分组成每次变更都会记入活动日志。卡片模型 的注释指出每一次截止日期的设置/修改都会由服务端before.update钩子写入一条activityType为a-dueAt的活动记录。只读计数方法。模型提供getDueDateChangeCount()getDueDateChangeCount() { const cardId this.isLinkedCard() ? this.linkedId : this.getRealId(); const activities ReactiveCache.getActivities({ cardId, activityType: a-dueAt, }); return Array.isArray(activities) ? activities.length : 0; }它从ReactiveCache中按cardIdactivityType: a-dueAt取出活动列表并计数对关联卡片会先解析出真实卡片 ID保证跨板引用场景下计数正确。 3.模板层渲染。全局注册的 dueDateChangeCount helper 在卡片对象不可用时返回 0从而让模板自然隐藏该行cardDetails.jade 中if dueDateChangeCount .card-details-due-date-changes(title{{_ due-date-changes}}) | {{_ due-date-changed-times dueDateChangeCount}}仅在计数非 0 时显示文案键due-date-changed-times已随多语言文件如 en 源文件 及各语种翻译全量覆盖。这条链路“update 钩子 → 活动记录 → ReactiveCache 查询 → 模板 helper → jade 渲染”全部是响应式的任何人再次修改截止日期活动集合变化会自动驱动详情面板中的计数更新无需任何手动刷新逻辑。五、一致性保障单元测试与回归文档强调“minicard 与卡片详情颜色必须一致”“恰好 48 小时算琥珀、超过即灰色”等精确行为这些约束在仓库中有对应的自动化验证dueDateColor.tests.js针对dueDateClass的纯函数单测覆盖“过去日期 → overdue”“48 小时内 → due-soon”“恰好 48 小时 → due-soon”“48 小时 1 分钟 → not-due#6000 回归”等边界dueDateCountdown.test.cjs针对倒计时文案逻辑的独立测试组件层测试如 cardDate.js 中注释提到的 #6615 数据上下文问题验证了详情视图传入{ card, canModifyCard }而迷你卡直接继承 Card 对象时两套调用形态都不会在响应式重跑中互相串扰。这些测试的存在意味着如果你在二次开发中调整了 48 小时阈值或颜色语义client/lib/tests/dueDateColor.tests.js会立即暴露与文档承诺不符的行为。六、小结WeKan 的截止日期功能以三个文档描述的特性为主线——四日期语义received/start/due/end、红/琥珀/灰三档颜色编码、截止日变更次数追踪——并在源码中落实为一套“纯函数判定 共享模板 活动日志计数”的清晰结构颜色规则集中在 client/lib/dueDateColor.js被详情与迷你卡两视图共用48 小时阈值以小时差闭区间判定倒计时按日历日计算并以 i18n 键输出保证多语言下“Due today / N days left / N days overdue”语义准确变更次数依赖服务端对a-dueAt活动的记录通过 models/cards.js 的只读计数方法暴露给 cardDetails.jade 渲染。理解这条从文档语义到dueDateClass、dueCountdown、getDueDateChangeCount的实现映射是掌握 WeKan 卡片日期体系、并在定制部署中正确扩展日期展示行为的关键。【免费下载链接】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),仅供参考