简介:一套融合jQuery、LayUI、Bootstrap与dhtmlxGantt的任务进度甘特图开发示例,适合具备一定前端基础、希望快速上手项目管理可视化场景的Web开发者。资源共499个文件,以139个js脚本、128个html页面、107个gif演示图为主,另含css、map、json、字体等资源,总量仅2.9MB,典型目录中可见bootstrap.css、layui.css及dhtmlxgantt_skyblue/material等多样主题样式文件。示例通过jQuery处理页面动态交互与数据请求,LayUI和Bootstrap搭建响应式后台界面,dhtmlxGantt承担核心甘特图展示与编辑能力,可完成任务的增删改、依赖关系设置和时间线缩放等操作。配套的html与gif便于对照页面效果,js与css便于二次开发,整体结构清晰,适合学习多种技术整合及甘特图定制方案。已有477人学习,对想要提升前端综合运用能力的开发者有直接参考价值。
1. 一个 gantt 示例压缩包里,真正难复制的不是甘特图
这种 rar 示例包,打开之后最值钱的往往不是那张渲染好的 gantt 大图,而是 jquery、layui、bootstrap、dhtmlxgantt 这四个库在一个存量后台里怎么不打架地各干各的。项目本身不新潮,但需求很常见:后台任务表要加一个可视化进度面板,能拖、能看、能编辑,还要求不推翻原有 JSP 或 jQuery 页面。
常见做法是把 dhtmlxgantt 塞进 layui 弹窗和 bootstrap 布局之间,用任务 JSON 把所有模块串起来。这条路径不是最优架构,却是改造老系统时成本最低、风险最小的方案,对维护传统管理后台、想加甘特图又暂时不换 Vue/React 的团队尤其合适。下面顺着这套技术栈,把数据模型、初始化参数、弹窗联动和进度刷新完整拆开讲,重点标出那些参数不统一就会整图变白的地方。
2. jquery、layui、bootstrap、dhtmlxgantt 各管一段,靠数据模型衔接
拿到示例包先别急着开页面,先看四个依赖的分工。标题里这串技术栈不是替身关系,不是非此即彼的选型,而是同一个页面上四个独立模块:jquery 负责底层 DOM 操作和 ajax 数据装载,bootstrap 提供栅格、按钮、进度条这类静态基础组件,layui 提供弹窗、日期、表单和表格,dhtmlxgantt 负责把任务进度画成可拖拽的调度图。它们职责不重叠,真正的衔接点是任务 JSON。
2.1 依赖职责划分:避免 bootstrap 和 layui 互相抢活
先把职责分清楚,后面写代码才不会纠结某个按钮到底该用谁的样式。
| 技术 | 主营业务 | 在本示例中承担 | 不负责 |
|---|---|---|---|
| jquery | DOM 操作、$.ajax | 任务数据装载、滚动到底部加载更多、按钮事件 | 不碰数据渲染 |
| bootstrap | 栅格布局、navbar、progress 进度条 | 页面骨架、顶部导航、任务条上的进度条 | 不做弹窗,避免和 layer 抢层级 |
| layui | layer 弹窗、laydate 日期、form 表单、table 表格 | 新增/编辑任务弹窗、左侧任务列表 | 不画甘特图 |
| dhtmlxgantt | 任务条、时间轴、拖动、缩放、依赖连线 | 整个甘特图视图及增删改后的刷新 | 不主动请求后端 |
一个容易翻车的点是 bootstrap 的 modal 和 layui 的 layer 同时出现在一个页面。两者都会往 body 里塞遮罩层,z-index 一旦被业务代码手动改过,弹窗就可能被遮罩盖住。我一般只保留 layer,把 bootstrap 的>{ "data": [ { "id": 1, "text": "需求评审", "start_date": "2025-03-01 09:00", "duration": 2, "progress": 1, "parent": 0 }, { "id": 2, "text": "接口联调", "start_date": "2025-03-03 09:00", "duration": 3, "progress": 0.4, "parent": 0 } ], "links": [ { "id": 1, "source": 1, "target": 2, "type": "finish_to_start" } ] }
任务字段的语义很直接,但有两个地方容易写错。progress 是 0 到 1 的小数,不是 0 到 100 的整数,写错后进度条会超出任务条。parent 表示父子关系,根任务的 parent 填 0,子任务填父任务的 id。
| 字段 | 含义 | 易错点 |
|---|---|---|
| id | 任务唯一标识 | 必须全局唯一,不能跨任务重复 |
| text | 任务名称 | 插入模板前要做 HTML 转义 |
| start_date | 开始时间 | 字符串格式必须匹配 xml_date |
| duration | 工期 | 与时间刻度单位一致 |
| progress | 完成度 | 取值 0~1,不是百分比 |
| parent | 父任务 id | 根任务填 0 |
links 里的 type 只有四个枚举值,控制任务之间的依赖关系:
| type 值 | 含义 |
|---|---|
| finish_to_start | 前一个完成后,后一个才能开始 |
| start_to_start | 两个任务必须同时开始 |
| finish_to_finish | 两个任务必须同时结束 |
| start_to_finish | 前一个开始后,后一个才结束 |
最常见的依赖是 finish_to_start,也就是图上的 A 结束指向 B 开始。写 links 时要保证 source 和 target 都在任务列表里存在,否则 gantt 只会在控制台告警,不会告诉你具体哪个边断了。
2.3 xml_date:时间字段与后端格式的接缝
这是示例里最容易翻车的一个配置。dhtmlxgantt 接收 Date 对象或配置了格式的字符串,默认格式是 "%d-%m-%Y",跟大多数后台返回的 "yyyy-MM-dd HH:mm:ss" 差得很远。常见做法是让后端直接返回 "2025-03-01 09:00" 这样的字符串,前端初始化时声明解析格式:
gantt.config.xml_date = "%Y-%m-%d %H:%i";注意 %i 是分钟,%H 是小时,这是 dhtmlxgantt 自己的格式化语法,跟 Java 的 SimpleDateFormat 不是一回事。如果后端给的是 "2025-03-01 09:00:00" 带秒的格式,而 xml_date 没配秒的对应项,时间会被解析成 Invalid Date,任务条直接画不出来。
我的习惯是后端在序列化阶段就统一好字符串格式,不依赖前端做二次转换。如果项目里后端返回的是时间戳,可以用 gantt.date.str_to_date 写一个自定义解析函数挂到数据读取链路上,但尽量别在 gantt.parse 之后再逐条修补时间字段,那样 latency 高还容易漏。
3. 初始化 dhtmlxgantt:最小页面骨架、依赖顺序与首次数据装载
标题里这串技术栈能不能跑起来,七成取决于静态资源引入顺序,三成取决于甘特图容器的高度。传统后台页面不是 SPA,不存在打包器帮你处理依赖树,所有库都靠 script 标签堆叠。顺序错了最常见的现象是 bootstrap 的下拉没反应,或者 dhtmlxgantt 的样式被 layui 的 reset 洗掉。
3.1 静态资源引入顺序与版本选择
页面里同时出现 bootstrap、layui、dhtmlxgantt 三套 CSS 时,引入顺序直接决定最终样式。推荐的顺序如下:
| 顺序 | 资源 | 原因 | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | bootstrap.min.css | 先定栅格和基础组件样式 | ||||||||||||||||||||||||||||||||||||||||||
| 2 | layui.css | 覆盖按钮、表格的局部变量 | ||||||||||||||||||||||||||||||||||||||||||
| 3 | dhtmlxgantt.css | 放最后,防止被前面的 reset 洗掉 | ||||||||||||||||||||||||||||||||||||||||||
| 4 | jquery.min.js | bootstrap 依赖它 | ||||||||||||||||||||||||||||||||||||||||||
| 5 | bootstrap.min.js | jquery 之后才能绑定><nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <span class="navbar-brand">任务进度看板</span> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-md-4"> <table id="taskTable" lay-filter="taskTable"></table> </div> <div class="col-md-8"> <div id="gantt"></div> </div> </div> </div> 左侧的 layui table 用来展示任务明细和做搜索过滤,右侧的高宽必须显式声明。很多人第一次跑示例包,甘特图容器高度写死 600px,窗口一缩小就出现滚动条乱跳。推荐用 calc 计算高度,撑满视口剩余空间: 如果甘特图不是放在页面顶层,而是藏在 bootstrap tab 或 layui tab 里,切换 tab 后容器宽度可能从 0 变成正常宽度,此时必须调用 gantt.setSizes() 重算尺寸,否则任务条全部挤在 1px 宽的容器里。 3.3 首次数据装载:gantt.load 与 $.getJSON 的取舍初始化代码很短,但顺序不能乱。先声明 xml_date,再 init,最后装载数据。装载方式有两种:gantt.load(url) 适合十行代码起步验证,直接把 URL 交给 gantt 内部 ajax;真实项目里更多用 $.getJSON,因为要带上统一鉴权头、错误提示和权限拦截。 parse 能自动识别 { data, links } 结构和 { tasks, links } 结构,不用手工拆分。clearAll 的作用是防止第二次查询后新旧数据叠加,导致同一任务出现两条重复的条。render 放在 parse 之后,数据变更完成后统一绘一次;频繁调用 render 会明显卡顿,批量更新场景下最后只调一次。 4. gantt 视图调参:columns、scales、tooltip 三组必改配置数据能装载只是第一步。示例包一旦要在真实项目里用,改动最多的一定是这三组配置:左侧任务列表列、顶部时间刻度轴、悬浮提示。这三块互相影响,列决定用户能看到哪些字段,刻度决定一个格子代表多长时间,tooltip 决定悬停时还能补多少信息。 4.1 columns:任务表格里每一列显示什么常见的列配置像下面这样,name 对应任务对象里的字段名: tree:true 是必须保留的配置项,只有它为 true 时左侧才会渲染层级缩进和展开收起箭头,子任务才能折叠。add 列是 dhtmlxgantt 内置的快捷入口,会在表格底部自动出现一个加号按钮,点击后调用 gantt.createTask 并把新任务挂在当前行层级下,比自己在外面加一个“新建任务”按钮更顺手。不想要这个入口时,把该列对象从数组里移除即可。 列宽配置有一个注意点:各列固定宽度之和不要超过容器宽度,否则最后一列会被压缩。给 text 列用 width:"*" 让它吃掉剩余空间,其他列按内容给固定值。 4.2 scales:时间刻度和单位怎么定时间刻度决定横向滚动时看到的单位,通常配两级,上面显示大单位,下面显示小单位: 这套配置下顶部刻度是年-月,底部刻度是月-日,每个最小列宽 32px。如果任务粒度是小时级,就把 unit 改成 "hour",format 改成 "%H:%i",duration 也按小时数给。
fit_tasks 设置为 true 后,横向时间轴会以第一条任务和最后一条任务的日期为边界,避免任务在 1 月、项目在 6 月时左侧空出一大片。如果希望右侧留出未来几天的空余,手动给 gantt.config.end_date 加一个值覆盖自动范围。 4.3 tooltip 和进度文本模板进度数字默认显示在任务条内部,格式是浮点数,直接显示 0.6 很难看。用模板统一改成百分比: tooltip_text 返回的是一个 HTML 字符串,会直接插进页面。task.text 如果来自用户输入,插入模板前必须做 HTML 转义,否则用户把 4.4 任务树排序和跨父节点拖拽排序和拖拽是一对容易混淆的配置。order_branch 控制的是同级任务之间的拖拽排序,sort_tree 控制加载时按某个字段自动排序。 order_branch_free 开启后,子任务可以拖到另一个父任务下面,gantt 会自动更新 task.parent 字段。拖拽结果要持久化时,后端保存的不只是任务的时间变化,还有 parent 和排序字段,这两个字段在图上不直观,最容易漏掉。drag_mode 设为 none 可以关闭所有拖拽,适合只有展示权限的只读视图。 5. layui 弹窗 + bootstrap 进度条:gantt 任务增删改查与进度刷新如果第一步数据能看到图,第二步就该让图上能改任务。dhtmlxgantt 自带 lightbox 弹窗,但长得很库味儿,跟 layui 的表单风格不一致。常见的做法是关闭自带弹窗,把编辑界面换成 layui layer + form,再用 bootstrap 的 progress 组件直观展示任务完成率。 5.1 关掉 lightbox,把双击事件接到 layui layer自带弹窗由 details_on_create 和 details_on_dblclick 两个开关控制,先关掉,再把双击事件挂到自己的函数上: setTimeout 延迟 0 毫秒是为了避开 dhtmlxgantt 内部对本次双击事件的捕获。不加这一层,layer 弹窗经常刚出现就被后续事件抢走焦点,表现为闪一下直接关闭,排查起来很费时间。 5.2 弹窗表单与 laydate 日期边界弹窗内容放在一个隐藏的 script 模板里,用 layer.open 读取这段 HTML: 打开弹窗时做两件事:回填表单、再渲染一次 layui 组件。很多示例包挂在回填后少了 form.render(),结果 select 下拉框没有皮肤、laydate 点击不弹日历。 laydate 的 max 参数就是热搜里经常出现的“最大日期当前日期”的写法,laydate.now(0, ...) 返回当前时间字符串,这样开始时间就不能选未来日期。如果业务允许排期在未来,把 max 去掉即可。
弹窗里如果还需要从接口拉取负责人列表,用 form.val 先给 select 赋值,再调用 form.render("select"),这就是 layui select 动态赋值 的标准套路。 5.3 保存时写回 task 并刷新进度模板保存按钮走 layui 的 submit 事件。字段读取、时间转换、进度换算都在这一个函数里完成: parseDate 的第二个参数填 "xml_date" 是 dhtmlxgantt 的快捷方式,等于用 gantt.config.xml_date 作为解析模板。不要直接把字符串赋给 task.start_date,gantt 后续所有时间计算都要求 Date 对象,字符串会直接报错。progress 从表单的 0~100 换算成 0~1,这一步漏了,进度条宽度就会超出任务条十倍。 5.4 bootstrap 进度条接进任务条bootstrap 基础组件里的 progress 进度条可以直接塞进 dhtmlxgantt 的任务条模板,比默认的纯色进度块好看,也跟页面风格统一: task_text 是任务条内容模板,默认只显示任务名,覆盖后返回什么就显示什么。progress 组件要压矮到 6px 左右才不会遮挡拖拽手柄,因为任务条默认高度有限,塞太多 DOM 会导致 resize 的手柄点不中。这里的模板在拖动任务条时也会跟随绘制,模板里不要写宽尺寸的绝对定位元素,否则拖拽时手柄经常被一层透明的 DOM 盖住。 左侧 layui table 如果想显示同样的进度条,可以在 table 的 templet 里复用这段 HTML,数据字段从行数据里读,两边的视觉就统一了。
6. gantt 交付前自检:一段断言脚本和五档参数清单甘特图在浏览器里能出图,和能交付给同事用,中间隔着一层数据完整性校验。我一般会在首次 render 后跑一段断言代码,把任务 ID 重复、依赖指向不存在的节点、进度越界、日期解析失败这四个最常见的坑一次暴露出来。 onDataUpdated 在每次数据变更后触发,开发环境直接输出结果。控制台出现 assertion failed 时,优先查后端映射出来的字段名,而不是去 gantt 配置里找原因。生产环境把 console.assert 换成错误提示接口即可。 最后是一组容易散落在配置里的参数清单,示例包交付前逐条核对一遍,比临时翻文档快得多:
最后一个交付技巧:如果甘特图被放在 bootstrap tab 或 layui tab 里,tab 切换后容器宽度从 0 变回正常宽度时,gantt 不会自动感知。在 tab 切换回调里执行 gantt.setSizes(),并在首次渲染后手动调用一次,任务条就不会全部挤在最左侧。把这条写进示例包的 README,能省下后续联调的大段排错时间。 本文还有配套的精品资源,点击获取 |