WeKan 甘特图(Gantt)全面指南:内置周视图、Frappe 与 dhtmlx 双引擎的配置与使用
【免费下载链接】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 是采用 Meteor 构建的开源看板应用,其在 2025-12-22 引入的全新 Gantt 功能,为看板卡片的时间管理提供了三种可视化方案:内置表格型周视图、基于 frappe-gantt 的条形图视图,以及基于 dhtmlx-gantt 的经典「网格+时间轴」视图。阅读本文后,你将掌握如何在"All Boards"页面进入 Gantt 视图、理解四种卡片日期(Received/Start/Due/End)在三种视图中的呈现与编辑方式,并能依据写入权限判断拖拽改期是否可用,同时了解其背后的源码实现原理。
一、Gantt 在 WeKan 中的定位与入口
Gantt 视图与 Swimlanes(泳道)、Lists(列表)、Calendar(日历)等视图并列,统一挂在看板顶部的Board View下拉菜单中。入口路径为:
Board View 菜单(顶部工具栏)→ Gantt / Gantt (Frappe) / Gantt (dhtmlx)
┌─ Board View ▾ ──────────────────────┐ │ ... │ │ ▸ Gantt <- here │ │ Gantt (Frappe) │ │ Gantt (dhtmlx) │ │ ... │ └───────────────────────────────────────┘在 boardHeader.jade 中可以看到,当boardView切换为board-view-gantt时,菜单项会渲染为fa-bar-chart图标,标识 Gantt 视图处于激活状态。三种视图共享同一份数据源——当前打开看板内所有卡片的四个日期字段。
二、内置表格型 Gantt:以周为单位的时间网格
内置 Gantt 是一张以「任务 × 周」为坐标的表格视图:
┌ Gantt ──────────────────────────────────────────────┐ │ Task | wk1 | wk2 | wk3 | wk4 | ... │ │ Card A | ▬▬▬▬▬▬ 🔵🟢🟡🔴 (date icons) │ │ Card B | ▬▬▬▬▬▬▬ │ └─────────────────────────────────────────────────────────┘周列的生成逻辑
视图的纵轴不是固定日历,而是只在当前看板存在卡片日期的周才生成列。在 gantt.js 中,getISOWeekInfo()依据 ISO 8601 标准计算某个日期所属的年-周编号(YYYY-Www),weeks()helper 遍历当前看板全部卡片,收集四个日期字段(receivedAt/startAt/dueAt/endAt)涉及的所有周,并按时间排序输出为列。因此一个没有任何日期卡片的看板,Gantt 表格会是空的。
四种日期在图中的呈现
表格按天细分到一周七天(weekDayshelper),每一天对应一个单元格。单元格通过cellContentClass与cellContent两个 helper 决定样式与图标(源码见 gantt.js):
| 卡片日期字段 | CSS 类名 | Font Awesome 图标 | 语义 |
|---|---|---|---|
receivedAt(收到日期) | ganttview-received | fa-inbox | 任务被接收/创建 |
startAt(开始日期) | ganttview-start | fa-rocket | 任务启动 |
dueAt(截止日期) | ganttview-due | fa-clock-o | 任务截止 |
endAt(结束日期) | ganttview-end | fa-flag-checkered | 任务实际完成 |
同时cellClasses会叠加ganttview-today(今天)与ganttview-weekend(周末)条件类,让当日与周末在视觉上可区分;只有命中四种日期的单元格才会附加js-gantt-date-icon,从而获得可点击编辑的能力。
点击交互:开卡与改期
- 点击任务名称:
click .js-gantt-card-title事件把行上的data-card-id写入模板的selectedCardId响应式变量,随即通过 ganttCard.jade 渲染出完整的cardDetails组件(即卡片详情弹层),无需跳转页面即可查看并编辑卡片。 - 点击日期图标:
onRendered中注册的委托事件监听.js-gantt-date-icon,读取单元格的data-date-type(去掉ganttview-前缀后)映射到对应的弹窗:received → editCardReceivedDate、start → editCardStartDate、due → editCardDueDate、end → editCardEndDate,然后调用Popup.open(popupName)打开日期编辑弹窗。这一设计决定了内置视图是「点击日期图标」式的编辑器,不支持拖拽改期。
三、内置 Gantt 的使用步骤
- 打开一个看板,确保其中至少一张卡片设置了 Received/Start/Due/End 中的任一日期;
- 点击顶部工具栏Board View→Gantt;
- 点击任务名称打开对应卡片;
- 点击行上四个日期图标(Received/Start/Due/End)中的任意一个,直接在图表中修改该日期。
四、两套第三方 Gantt 引擎:Frappe 与 dhtmlx
除内置表格视图外,同一个Board View菜单还提供基于第三方库的两个渲染器,对应模板分别为 frappeGantt.jade 与 dhtmlxGantt.jade。
三者读取的卡片数据完全相同(同一看板卡片的四个日期),区别在于渲染库、交互手感,以及是否支持拖拽条形图改期:
┌─ Board View ▾ ──────────────────────┐ │ ▸ Gantt │ │ ▸ Gantt (Frappe) <- here │ │ ▸ Gantt (dhtmlx) <- or here │ └───────────────────────────────────────┘两个引擎都由 client/features/gantt.js 统一导入,属于按需挂载的 Board View 组件。
4.1 Gantt (Frappe)
Frappe 引擎基于 frappe-gantt。
核心机制:
- 懒加载:库通过动态
import('frappe-gantt')加载(loadGanttLib()),只有用户真正打开该视图时代码才进入浏览器,不会拖慢首屏。由于 frappe-gantt 的package.jsonexports 映射只暴露了"style"条件、不接受./dist/frappe-gantt.css子路径,其 CSS 被原样复制为 frappeGanttLib.css 静态加载。 - 条形图生成:
cardsToTasks()把每张卡片转换为一个任务条:以startAt(缺失时回退到receivedAt)作为条形起点,以dueAt(缺失时回退到endAt)作为终点;若终点早于或等于起点,则自动补足为起点次日(addDays(start, 1))。_startField/_endField记录了条形两端实际代表的字段,供拖拽回写时精确定位。 - 拖拽改期与权限:
readonly_dates: !Utils.currentUserCan('write', board)——只有看板写入权限的用户才能拖动条形的左右边缘改期;无写权限时视图仍可读但不可拖。这一权限口径与 WeKan 其余模块的Utils.canModifyCard一致。 - 进度与逾期:卡片已有
endAt时progress为 100,否则为 0(进度条不可拖,readonly_progress: true);逾期任务(有dueAt、无endAt且已过截止日)会附加gantt-task-overdue样式类。 - 视图模式:默认以周(
WEEK)展示,且开启view_mode_select: true提供 Day/Week/Month/Year 切换下拉框;Frappe 内置的 7 种VIEW_MODE会被复制并逐一替换为 WeKan 的翻译文案(translatedViewModes()),其头部硬编码的 "Today" 按钮与 "Mode" 占位符则由MutationObserver(translateFrappeChrome())在每次重建时重新翻译。 - 弹出信息:点击条形的弹出面板中按行展示全部四个日期(
popupDetailsHtml()),因此即使某卡片通过回退字段仅用两个日期绘制条形,另外两个日期依然可见。 - 点击条形开卡:
on_click回调通过FlowRouter.go('card', ...)跳转到对应卡片页。
4.2 Gantt (dhtmlx)
dhtmlx 引擎基于 dhtmlx-gantt Community Edition,采用经典的「左侧任务网格 + 右侧时间轴」布局,交互手感与 Frappe 明显不同。
核心机制:
- 单例特性:dhtmlx-gantt 暴露的是一个共享的 JavaScript单例对象(
gantt),而非可按容器实例化的类。因此每次重新渲染及模板销毁时都必须先gantt.destructor()销毁(见onDestroyed),再在下次打开时重建,否则第二次打开会复用第一次遗留的实例状态——这是与 Frappe 引擎最本质的工程差异。 - 只读与权限:
gantt.config.readonly = !Utils.currentUserCan('write', board),同样以看板写权限控制拖拽。 - 任务数据:
cardsToTasks()按 dhtmlx 的任务结构输出{id, text, start_date, duration, progress},duration以整天计(Math.max(1, Math.round((end - start) / 86400000)));同样以 Start→Due(或 Received/End 回退)确定条形区间,并用_startField/_endField记录边缘对应字段。逾期任务条形渲染为#e74c3c红色。 - 拖拽回写:监听
onAfterTaskDrag事件(而非更通用的onAfterTaskUpdate,避免在响应式重渲染触发的gantt.parse()时误写回),仅在真实拖拽/缩放时按_startField/_endField调用card.setReceived/card.setStart/card.setEnd/card.setDue之一写回数据库。 - 交互收敛:双击弹出的编辑灯箱(lightbox)被
onBeforeLightbox => false禁用——卡片标题等字段的编辑统一在卡片详情页完成,图表内仅保留「点击开卡、悬停看详情、拖拽改期」三种交互。 - 本地化:
gantt.i18n.setLocale(dhtmlxLocaleFor(gantt, TAPi18n.getLanguage()))在存在内置语言包时使用 dhtmlx 自带 locale,否则用 Intl 动态生成日期名称;同时date_format设为'%Y-%m-%d %H:%i'。对于非公历日历系统(如希伯来历、伊斯兰历),还会整体替换时间刻度与日期模板为formatDateForDisplay的显示格式(见dateDisplayPreferences().calendarSystem !== 'gregorian'分支)。 - 悬停提示:
tooltip_text模板把全部四个日期逐行展示在悬停提示框中。
五、使用 Frappe / dhtmlx Gantt 的步骤
- 打开一个看板,确保卡片设置了 Start/Due(或 Received/End)日期;
- 点击顶部工具栏Board View→Gantt (Frappe)或Gantt (dhtmlx);
- 点击卡片的条形图打开对应卡片;
- 拥有看板写入权限时,拖动条形图边缘即可直接修改该卡片日期,改动会写回条形边缘所代表的那个日期字段;
- 无写入权限时图表仍可浏览,只是不可拖拽。
六、前置条件与权限边界
- 卡片至少需要设置 Received/Start/Due/End 中的一个日期,才会出现在三种 Gantt 视图之一;四个日期都没有的卡片会被跳过(
cards.filter(c => c.receivedAt || c.startAt || c.dueAt || c.endAt),见 gantt.js)。 - 拖拽改期(仅 Frappe/dhtmlx 两个引擎支持)要求看板写入权限;内置表格视图则始终通过点击日期图标弹窗改期。
- 无论哪种视图,日期变更都会落回卡片的真实字段,与其他视图(如日历视图的拖拽编辑)共享同一套写回方法。
七、数据模型与写回原理
Gantt 依赖的四个日期字段定义于 models/cards.js(receivedAt等),对应的读写方法同样集中在该文件中:
setReceived(receivedAt)— models/cards.jssetStart(startAt)— models/cards.jssetDue(dueAt)— models/cards.jssetEnd(endAt)— models/cards.js
从源码看,这些方法都先通过getRealId()解析真实文档 ID(兼容「关联卡片/关联看板」的链接场景),再以Cards.updateAsync或Boards.updateAsync写回$set。Frappe 与 dhtmlx 引擎的拖拽回写、日历视图的拖拽编辑(见 boardBody.js 的eventDrop/eventResize)最终都汇入这四个方法,保证了不同视图间日期语义的一致性。此外,每次修改截止日期都会产生一条a-dueAt类型的活动记录,getDueDateChangeCount()(models/cards.js)据此统计截止日期被改动的次数,用于卡片详情中展示截止日期变更的责任归属。
八、旧版 GPL Gantt 的弃用与 UCS 平台
早先基于 GPLv2 的 WeKan Gantt(wekan-gantt-gpl)现已弃用,不再作为推荐方案使用;新的 MIT 协议 Gantt 功能从 2025-12-22 起随 MIT WeKan 主仓库发布。在 UCS(Univention Corporate Server)容器化部署场景下,原文档也标注了 Gantt 功能的对应章节入口,仓库中提供了一份 UCS 环境运行 WeKan 的演示视频(docs/Platforms/FOSS/Container/UCS/UCS-4.4-with-wekan-10.200.2.25.webm)可供参考。
九、源码索引
- 入口装配:client/features/gantt.js
- 内置表格视图模板:gantt.jade、样式 gantt.css
- 卡片详情弹层:ganttCard.jade、ganttCard.js
- Frappe 引擎:frappeGantt.js、frappeGantt.jade
- dhtmlx 引擎:dhtmlxGantt.js、dhtmlxGantt.jade
- 本地化映射:ganttLocale.js
- 日期字段模型:models/cards.js
总结来说,WeKan 的三套 Gantt 视图共享同一数据模型,只是交互层次不同:内置表格视图适合快速浏览与逐项点选日期,Frappe 引擎适合轻量拖拽排期(零依赖、按需加载),dhtmlx 引擎则提供最接近桌面项目管理软件的「网格+时间轴」体验。理解四日期字段与写回方法,是进一步定制或二次开发这些视图的起点。
【免费下载链接】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),仅供参考