尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

jQuery+layui+Bootstrap+DHTMLX Gantt构建任务进度甘特图

发布时间:2026/9/16 18:45:58

资讯中心
01
ARTICLE

jQuery+layui+Bootstrap+DHTMLX Gantt构建任务进度甘特图

jQuery+layui+Bootstrap+DHTMLX Gantt构建任务进度甘特图
简介一个结合jQuery、LayUI、Bootstrap与dhtmlxGantt四种前端技术的甘特图开发示例面向需要任务进度可视化和项目管理的Web开发者。资源包共499个文件大小约2.9MB包含139个js脚本、128个html页面、35个css样式并附多款dhtmlxGantt主题样式、动态演示gif、SQL初始数据与MPP项目文件便于直接查看运行效果和二次开发。已有477人学习下载。示例重点展示了dhtmlxGantt的任务管理、依赖关系、拖放操作及时间轴缩放等核心能力同时结合jQuery动态处理数据交互利用LayUI与Bootstrap搭建响应式后台界面。通过学习这套代码可以快速搭建一套任务进度处理系统对开发项目管理工具或需要可视化进度的Web应用都具有切实参考价值。1. 为什么用jquerylayuibootstrapdhtmlxgantt拼任务进度甘特图后台管理系统做到一定阶段任务进度可视化几乎是必备模块。原生的时间线手写难度不高但要做到拖拽、依赖线、按时长渲染任务条代码量和测试成本都会成倍上涨。DHTMLX Gantt把甘特图最核心的渲染和交互封装好了但它默认的皮肤和后台常用组件并不统一需要一套周边库来补齐表单、弹窗和页面框架。jQuery负责事件和Ajaxlayui处理日期控件、下拉框、滑块和弹层bootstrap提供栅格、模态框和基础组件dhtmlxgantt只关心图本身。这套组合最大的价值是不需要把项目迁移到新框架老后台页面引入几个静态文件就能跑出一张可交互的任务进度甘特图。适合已有bootstrap或layui后台、想快速交付任务进度模块的团队也适合前端基础较弱、想少踩坑的开发者。2. 搭好页面骨架bootstrap布局下用dhtmlxgantt渲染第一张任务进度图2.1 四个库的引入顺序与职责边界先明确各自的活。bootstrap只能管网格、按钮、模态框这类静态基础组件layui负责动态的表单控件比如日期、下拉框、滑块和layer弹层jQuery是地基管事件绑定、Ajax请求和DOM操作dhtmlxgantt负责甘特图的时间轴、任务条和依赖线。这样划分后每个库改版时影响范围都能缩小。引入顺序要按依赖关系排jQuery必须在最前面bootstrap和layui的js都依赖jQuery如果页面里同时用了bootstrap和layui的弹层尽量让bootstrap的css先加载layui的css后加载因为layui的弹层样式更完整后加载能覆盖部分bootstrap默认样式。!-- head里先放样式 -- link relstylesheet hrefstatic/bootstrap/css/bootstrap.min.css link relstylesheet hrefstatic/layui/css/layui.css link relstylesheet hrefstatic/gantt/dhtmlxgantt.css !-- body结束前放脚本 -- script srcstatic/jquery/jquery.min.js/script script srcstatic/bootstrap/js/bootstrap.min.js/script script srcstatic/layui/layui.js/script script srcstatic/gantt/dhtmlxgantt.js/script代码逻辑说明bootstrap.min.js依赖jQuery所以要等jquery.min.js执行完再加载。dhtmlxgantt.css放在最后是想让甘特图里的大部分尺寸不被bootstrap的box-sizing规则影响到。但实践中bootstrap会强制给页面元素设置box-sizing: border-box这会让甘特图的任务条宽度少算边框整体偏移1到2个像素。遇到这种情况写一条覆盖规则.gantt_task_cell, .gantt_task_row, .gantt_task_content { box-sizing: content-box; }注意这段覆盖规则要放在dhtmlxgantt.css之后否则会被同类选择器覆盖。如果甘特图版本较新内部已经兼容了border-box可以不加这段但加上也基本无损。2.2 初始化甘特图的最小代码与数据字段DHTMLX Gantt渲染一张图需要三样东西一个容器div、一份任务数据、一次gantt.init。示例.rar里最常见的结构是父子数据父任务表示阶段子任务表示具体工作项。div idgantt_here stylewidth: 100%; height: 600px;/divconst tasks { data: [ { id: 1, text: 需求评审, start_date: 2024-11-01 09:00, duration: 2, progress: 1, parent: 0 }, { id: 2, text: 技术方案, start_date: 2024-11-03 09:00, duration: 3, progress: 0.6, parent: 0 }, { id: 3, text: 后端开发, start_date: 2024-11-06 09:00, duration: 10, progress: 0.3, parent: 2 }, { id: 4, text: 前端开发, start_date: 2024-11-08 09:00, duration: 8, progress: 0, parent: 2 } ], links: [ { id: 1, source: 1, target: 2, type: 0 } ] }; gantt.config.date_format %Y-%m-%d %H:%i; gantt.init(gantt_here); gantt.parse(tasks);逻辑说明data数组里每条记录代表一个任务start_date是计划开始时间duration是工期天数progress是0到1的小数1代表100%完成parent为0表示顶层任务否则是父任务id。links定义任务依赖关系type为0代表“结束到开始”即前置任务结束后后置任务才能开始。参数说明gantt.config.date_format是DHTMLX Gantt自己的一套格式符%Y是四位年份%m是两位月份%d是日期%H:%i是时分。很多人误用JavaScript的YYYY-MM-DD结果日期解析成NaN任务条全部消失。这个配置要和后台返回的字符串格式严格一致否则gantt.parse会用当前时间兜底造成甘特图时间轴错乱。时间刻度也可以在这里一并调好。任务粒度是天就用scale_unit指定主刻度subscales指定副刻度gantt.config.scale_unit day; gantt.config.step 1; gantt.config.date_scale %d %M; gantt.config.subscales [ { unit: month, step: 1, date: %Y, %F } ];scale_unit是主时间单位step是主刻度跨越几个单位date_scale控制主刻度标签显示格式subscales是次级刻度。任务跨度超过30天时把主刻度改成week副刻度改成day横轴会更舒服。这些配置要在gantt.init之前设置初始化后再改不会生效。任务字段用下面这张表做参考示例数据里一般不会全部出现但项目落地时大概率都要补字段名类型说明是否必须idnumber任务唯一标识父任务和依赖都靠它关联是textstring任务名称显示在左侧列是start_datestring计划开始时间按date_format解析是durationnumber工期单位由duration_unit控制默认天是progressnumber0~1小数0.3表示已进行30%否parentnumber父任务id0为顶层任务否ownerstring自定义字段负责人否statusstring/number自定义字段业务状态否2.3 把示例数据换成jQuery的Ajax接口演示数据写死在JS里方便但真实项目要从后台拉取。最直接的做法是把gantt.parse包进$.ajax的done回调里。后台返回的数据结构通常是{code: 0, data: {tasks, links}}需要按实际结构调整。function loadGantt() { $.ajax({ url: /api/task/plan, type: GET, dataType: json }).done(function (resp) { if (resp.code 0) { gantt.clearAll(); gantt.parse(resp.data); } else { layer.msg(resp.msg, { icon: 2 }); } }).fail(function () { layer.msg(任务计划加载失败, { icon: 2 }); }); }这里用layui的layer.msg做提示而不是bootstrap的alert因为alert会阻塞渲染且样式无法统一。clearAll强制清空旧数据再解析防止之前的分页数据或点击测试数据残留。需要注意时间格式统一。后台如果返回时间戳要先把时间戳转成YYYY-MM-DD HH:mm字符串。DHTMLX Gantt也支持%s格式但只认秒级时间戳毫秒要自己除以1000。我一般在后端返回时就格式化好前端不做二次转换避免时区偏差。3. jQuerybootstrap把甘特图交互接到任务进度处理上3.1 用bootstrap模态框接管任务新增与编辑DHTMLX Gantt自带一个lightbox弹窗任务字段少时够用但业务系统里常有负责人、状态、优先级这些额外字段lightbox里自定义字段的成本不低。常见做法是关闭lightbox改用bootstrap modal让表单样式和页面其他部分保持一致。gantt.config.details_on_create false; gantt.config.details_on_dblclick false; gantt.attachEvent(onTaskClick, function (id) { const task gantt.getTask(id); $(#taskForm [nameid]).val(task.id); $(#taskForm [nametext]).val(task.text); $(#taskForm [nameduration]).val(task.duration); $(#progressInput).val(Math.round(task.progress * 100)); $(#progressText).text(Math.round(task.progress * 100) %); $(#taskModal).modal(show); return false; });逻辑说明details_on_createfalse和details_on_dblclickfalse分别关掉新建时和双击时弹出的lightboxonTaskClick里返回false可以阻止单击时的默认弹窗。gantt.getTask(id)拿到的是内存里的任务对象直接取字段赋给表单。注意progress要乘以100再填滑块因为layui滑块一般用0到100的整数。保存时用jQuery收集表单内容根据id是否存在决定调用gantt.updateTask还是gantt.createTask$(#saveTaskBtn).on(click, function () { const taskId $(#taskForm [nameid]).val(); const task { text: $(#taskForm [nametext]).val(), start_date: $(#taskForm [namestart_date]).val(), duration: parseInt($(#taskForm [nameduration]).val(), 10), progress: parseInt($(#progressInput).val(), 10) / 100 }; if (taskId) { const target gantt.getTask(parseInt(taskId, 10)); target.text task.text; target.start_date task.start_date; target.duration task.duration; target.progress task.progress; gantt.updateTask(target.id); } else { gantt.createTask(task); } $(#taskModal).modal(hide); });说明gantt.updateTask(id)会根据内存中已存在task对象刷新甘特图所以要先把新值赋给gantt.getTask拿到的对象再调用updateTask。直接调用gantt.parse整体重绘也能达到效果但会破坏视图的滚动位置和展开状态。新建任务时gantt.createTask(task)默认挂到根级别如果想挂到某个父任务下用gantt.createTask(task, parentId)。progress从滑块读出来的是0到100必须除以100否则90进度会渲染成5000%。3.2 layui滑块调整任务进度并实时回写任务进度处理里最常见的操作是拖一个进度条看百分比变化。layui的slider组件做这件事比input[typerange]好看得多而且自带主题色和回调。let progressSlider null; layui.use([slider], function () { progressSlider layui.slider.render({ elem: #progressSlider, min: 0, max: 100, value: 0, step: 5, theme: #1E9FFF, change: function (value) { $(#progressText).text(value %); $(#progressInput).val(value); } }); });change回调在滑块拖动松开后触发适合保存场景如果想在拖动过程中实时更新进度文本要改用schoosing事件。step: 5让进度按5%递进适合粗粒度估算如果是按工时汇报改成1。关键坑在任务切换打开编辑弹窗时不能只给隐藏input赋值要用progressSlider.setValue(progress)否则滑块UI还停留在上一次的位置。对应的初始化函数这样写function setProgressValue(percent) { $(#progressInput).val(percent); $(#progressText).text(percent %); if (progressSlider) { progressSlider.setValue(percent); } }3.3 jQuery滚动到底部加载更多任务任务超过几百条时一次性渲染会让拖拽和缩放都有明显延迟。最简单有效的方案是分页先加载最近30天或前50条任务等用户滚动到底部时再加载下一批。DHTMLX Gantt没有内置的分页接口但它的滚动区域可以像普通DOM一样监听。let currentPage 1; $(#gantt_here .gantt_ver_scroll).on(scroll, function () { const el this; if (el.scrollTop el.clientHeight el.scrollHeight - 80) { if ($(el).data(loading)) return; $(el).data(loading, true); loadMoreTasks(function () { $(el).data(loading, false); }); } }); function loadMoreTasks(callback) { $.ajax({ url: /api/task/plan/paged, data: { page: currentPage, pageSize: 50 }, dataType: json }).done(function (resp) { gantt.parse(resp.data, extend); callback(); }).fail(function () { layer.msg(加载更多失败, { icon: 2 }); callback(); }); }逻辑说明.gantt_ver_scroll是DHTMLX Gantt新版里的垂直滚动条容器旧版本的类名可能是.gantt_task或.gantt_data_area如果选择器不生效先打开浏览器开发者工具找到滚动元素真实类名再替换。gantt.parse(data, extend)的第二个参数extend表示追加数据而不是清空重画这是分页加载的关键。参数说明判断滚动到底部的余量写了80像素这个值根据感受调整太大会提前加载太小会看到明显的加载空白。用data(loading)做节流标记防止滚动事件在底部连续触发时发出多个重复请求。另外分页请求最好带上当前时间范围和父任务id否则新加载的子任务可能因为父任务还没加载而静默挂在根节点。4. layui日期控件和select动态赋值在甘特图表单里的联动4.1 layui date 最大日期当前日期限制计划时间范围甘特图里的任务开始日期一般不允许晚于某个截止日也不允许太随意。layui的laydate用max参数控制最大可选日期配合min可以圈出一个合理的计划区间。layui.use(laydate, function () { const laydate layui.laydate; const today laydate.now(); laydate.render({ elem: #startDate, format: yyyy-MM-dd HH:mm, min: -15d, max: today 23:59:59, done: function (value) { $(#startDate).val(value); calcEndDate(); } }); });逻辑说明laydate.now()返回的是2024-01-01这样的日期字符串如果不拼23:59:59max会变成当天零点用户选择今天下午的时间会被禁止。min: -15d表示允许选择15天前的日期适合补录历史任务。done回调在日期选定时触发这里主要做一件事把选好的值写回表单同时联动计算结束日期。任务进度处理的联动里常用结束日期开始日期工期用户在modal里改了开始日期后结束日期输入框需要跟着变function calcEndDate() { const start $(#startDate).val(); const days parseInt($(#duration).val(), 10) || 1; if (!start) return; const d new Date(start.replace(/-/g, /)); d.setDate(d.getDate() days); const month String(d.getMonth() 1).padStart(2, 0); const day String(d.getDate()).padStart(2, 0); $(#endDate).val(d.getFullYear() - month - day 18:00:00); }这里有一个跨平台坑在iOS Safari里new Date(2024-11-01 09:00)可能解析失败而new Date(2024/11/01 09:00)更可靠。所以replace横杠为斜杠是习惯写法。甘特图里结束日期是动态算出来的不需要写进任务对象保存时DHTMLX Gantt会根据start_date和duration自己算结束时间。4.2 编辑弹窗里给layui select动态赋值并重渲表单任务状态、负责人、优先级这类字段用layui select渲染不能直接$(#status).val(1)赋值。因为layui把原生select隐藏另建了一套div结构。正确做法是用form.val批量赋值再调用form.render(select)让图层刷新。layui.use(form, function () { const form layui.form; form.on(submit(saveTask), function (data) { // 这里处理保存data.field包含表单所有值 const task data.field; return false; }); }); function openEditModal(task) { layui.form.val(taskForm, { id: task.id, text: task.text, status: task.status || 0, owner: task.owner || , priority: task.priority || normal }); layui.form.render(select); }form.val(taskForm, {...})的第一个参数是表单容器上的lay-filter第二个参数是赋值对象。赋完值必须紧跟form.render(select)否则下拉框选中项是高亮样式但提交时原生select值可能还是旧值。select的选项数据如果是动态加载的比如负责人列表从接口拿要注意时机先渲染选项再form.render(select)然后再form.val赋值。顺序反了会导致选中不生效。常见做法是$.get(/api/user/list, function (resp) { const opts resp.data.map(function (u) { return option value u.id u.name /option; }); $(#ownerSelect).html(opts.join()); layui.form.render(select); if (task.owner) { layui.form.val(taskForm, { owner: task.owner }); layui.form.render(select); } });这个模式在“新增/编辑共用一套下拉选项”时特别有用。第一次渲染下拉第二次只赋值并重渲染可以避免选项还没加载完就被赋值覆盖。4.3 layui日期控件点击后日历面板位置调整与bootstrap静态下拉菜单在bootstrap modal里打开laydate最容易遇到的问题就是日历面板定位错位面板飘到页面左上角或者盖在弹层下面。原因是laydate默认用position: absolute定位而modal本身含有transform属性时absolute定位的参考容器会变成modal坐标计算就乱了。解决办法是给laydate传入position: fixed并把zIndex调到一个足够大的值laydate.render({ elem: #startDate, position: fixed, zIndex: 99999999, trigger: click });trigger: click表示点击输入框弹出默认是focus在modal里focus可能会触发两次弹出造成面板抖动。加了position: fixed后面板会相对于浏览器视口定位不再受modal的transform影响。如果还不行在bootstrap modal的shown.bs.modal事件里重新初始化一次laydate一般能解决大部分定位问题。至于“jquery 模拟 bootstrap html静态下拉菜单”这是一个常见需求不引入bootstrap的dropdown.js但希望静态下拉菜单能展开收起。做法很简单jQuery直接切换open或show类$(document).on(click, .dropdown-toggle, function (e) { $(this).parent().toggleClass(open); e.stopPropagation(); }); $(document).on(click, function () { $(.dropdown.open).removeClass(open); });逻辑说明第一次点击.dropdown-toggle时给父容器加open类下拉菜单通过CSS中的.open .dropdown-menu规则显示出来第二次点击其他地方时removeClass(open)收起。这里用事件委托保证动态创建的下拉按钮也能生效。bootstrap 4把open类换成了show所以代码要相应改成toggleClass(show)。这个技巧在你只想用bootstrap基础组件、不想再引入一堆JS时很实用。5. dhtmlxgantt任务进度处理的参数微调与数据验证5.1 自定义任务条上的进度文本甘特图默认在任务条里画一条进度填充但文本格式固定。想让看板更直观可以自己控制进度百分比展示gantt.templates.task_progress function (start, end, task) { return Math.round(task.progress * 100) %; };这个模板函数返回的字符串会显示在任务条上。函数接收的start、end是任务的时间范围task是任务对象。如果任务条宽度很小百分比文本会溢出可以用CSS裁剪.gantt_task_content { overflow: hidden; white-space: nowrap; }5.2 数据校验不让progress越界和孤儿节点吃掉任务任务进度处理里最容易出现两类脏数据progress不在0到1之间parent指向不存在的任务。我在gantt.parse之前总会跑一遍清洗函数function normalizeTasks(tasks) { tasks.data.forEach(function (task) { task.progress Math.min(1, Math.max(0, task.progress)); task.duration (task.duration task.duration 0) ? task.duration : 1; if (task.parent !tasks.data.some(function (t) { return t.id task.parent; })) { task.parent 0; } }); return tasks; }如果接口返回的progress本来就是50不是0.5这个函数会把50压成1任务看起来100%完成。所以清洗前最好明确接口约定。我更建议在后端就统一成小数前端只负责展示。孤儿节点的处理上DHTMLX Gantt不会报错但子任务会默默消失清洗后再parse可以省去很多排查时间。5.3 用gantt.serialize做本地备份和字段核对调试任务进度模块时最直接的验证办法是把当前甘特图数据序列化成JSON打印出来看任务和依赖是否完整$(#exportBtn).on(click, function () { const json gantt.serialize(); const backup JSON.stringify(json, null, 2); console.log(backup); const blob new Blob([backup], { type: application/json }); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download gantt-backup.json; a.click(); });gantt.serialize()返回的对象带有data和links结构可以直接喂给gantt.parse。做保存接口时把json.data里每条任务的progress乘以100再提交后端按百分比存储。如果进度精度到小数点后一位格式化时用toFixed(1)避免浮点误差。最后一个实用技巧把序列化JSON和修改后的HTML一起存成示例.rar同款结构下次重装环境时不用重新点一遍面板就能复现甘特图当前状态。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。