dhtmlxGantt 与 Salesforce LWC
本教程描述了如何将 dhtmlxGantt 集成到 Salesforce 的 Lightning Web Component 中。
请在以下的在线演示中查看与 Salesforce LWC 集成 DHTMLX 组件的示例:[在线演示](https://dhtmlx-dev-ed.develop.lightning.force.com/) 演示的源代码在 GitHub 上提供。
如果你使用的是其他技术,请查看下方可用的集成变体列表:
- dhtmlxGantt 与 ASP.NET Core
- dhtmlxGantt 与 ASP.NET MVC
- dhtmlxGantt 与 Node.js
- dhtmlxGantt 与 Python
- dhtmlxGantt 与 PHP: Laravel
- dhtmlxGantt 与 PHP:Slim
- dhtmlxGantt 与 Ruby on Rails
我们将使用 Salesforce CLI 来创建 Lightning Web Component 并将其上传到一个组织中。你也可以在 Visual Studio Code 中安装 Salesforce Extension Pack" 以便在开发组织中工作。
本教程所创建的演示的完整源代码 可在 GitHub 上获取。
你可以观看视频指南,演示如何用 Salesforce LWC 创建甘特图。
前提条件
如果你还没有安装,请 安装 Salesforce CLI。安装指南请参阅 这篇文章。
第 1 步:创建一个项目
如果你还没有开发者账户,请 注册 一个免费账户。安装指南请参考 这篇文章。
在左侧的搜索栏中,找到并选择 Dev Hub:

在新设置窗口中,选择 Enable Dev Hub:

让我们为 Salesforce DX 项目创建一个基础目录:
$ mkdir ~/salesforce
通过 CLI 创建一个 Salesforce DX 项目:
$ cd ~/salesforce
$ sfdx project generate -n gantt-salesforce-app
target dir = C:UsersUsersalesforce
create gantt-salesforce-appconfigproject-scratch-def.json
create gantt-salesforce-appREADME.md
create gantt-salesforce-appsfdx-project.json
create gantt-salesforce-app.huskypre-commit
create gantt-salesforce-app.vscodeextensions.json
create gantt-salesforce-app.vscodelaunch.json
create gantt-salesforce-app.vscodesettings.json
create gantt-salesforce-appforce-appmaindefaultlwc.eslintrc.json
create gantt-salesforce-appforce-appmaindefaultaura.eslintrc.json
create gantt-salesforce-appscriptssoqlaccount.soql
create gantt-salesforce-appscriptsapexhello.apex
create gantt-salesforce-app.eslintignore
create gantt-salesforce-app.forceignore
create gantt-salesforce-app.gitignore
create gantt-salesforce-app.prettierignore
create gantt-salesforce-app.prettierrc
create gantt-salesforce-appjest.config.js
create gantt-salesforce-apppackage.json
前往创建的项目:
$ cd gantt-salesforce-app
第 2 步:授权
使用 Web 服务器流 Authorize an Org:
$ sfdx org login web -d
Successfully authorized ... with org ID ...
更新你的项目配置文件 (sfdx-project.json)。将 "sfdcLoginUrl" 参数设置为你的 “My Domain URL”。你可以在 “My Domain” 设置页面找到组织的“域名”URL。例如:

"sfdcLoginUrl" : "https://xbs2-dev-ed.my.salesforce.com"
创建一个 Scratch Org:
$ sfdx org create scratch -f config/project-scratch-def.json -d
Creating Scratch Org...
RequestId: 2SR5j0000006JhCGAU
(https://xbsoftware2-dev-ed.my.salesforce.com/2SR5j0000006JhCGAU)
OrgId: 00DH40000000s0D
Username: test-tc0telfqhudt@example.com
✓ Prepare Request
✓ Send Request
✓ Wait For Org
✓ Available
✓ Authenticate
✓ Deploy Settings
Done
Your scratch org is ready.
第 3 步:将甘特组件添加到 Salesforce
为了开始使用该库,我们需要将其上传到 Salesforce 作为一个静态资源(Static Resource)。因此,打开你的 scratch org:
$ sfdx org open
现在,打开 “Static Resources” 标签并点击 “New” 按钮

给它起一个有意义的名称(我们使用 "dhtmlxgantt7111"),选择包含库本身的 ZIP 归档(该归档必须包含 dhtmlxgantt.js 与 dhtmlxgantt.css 文件),并选择 "Public" 缓存控制以提高性能。点击 “Save” 按钮。

现在我们在 Salesforce 内部拥有 dhtmlxGantt。

第 4 步:创建数据模型
dhtmlxGantt 的核心实体是 Tasks(任务)和 Links(链接)。一个好的做法是将 dhtmlxGantt 实体的所有属性作为纯 JSON 存储在 Salesforce 中。让我们创建 Tasks 与 Links 对象。打开对象管理器并选择 “Create” 然后选择 “Custom Object”:

Task 对象
为任务对象命名,设为 GanttTask/GanttTasks。

记录名必须与对象名称匹配,例如:
对象名称: GanttTask => 记录名称: GanttTask Name
点击 “Save” 按钮。
对象创建后,打开 “Fields & Relationships” 选项卡。点击 “New” 按钮。

- Duration
将数据类型选择为 “Number”,然后点击 “Next”。

将其命名为 “Duration”。它用于存储 JSON 序列化的任务属性。点击直到出现 “Save & New” 按钮。

点击 “Next” 按钮(接受默认设置),直到出现 “Save & New” 按钮。
- Parent
创建一个 “Parent” 字段。将数据类型选为 “Text”。

点击 “Next” 按钮(接受默认设置),直到出现 “Save & New” 按钮。
- Progress
创建一个 “Progress” 字段。将数据类型选为 “Number”。

点击 “Next” 按钮(接受默认设置),直到出现 “Save & New” 按钮。
- Start date
创建一个 “Start Date” 字段。将数据类型选为 “Date/Time”。

点击 “Next” 按钮(接受默认设置),直到出现 “Save” 按钮。
最后它应该看起来像这样:

Link 对象
打开对象管理器,选择 “Create” 然后 “Custom Object”:
为链接对象命名,设为 GanttLink/GanttLinks。

记录名必须与对象名称匹配,例如:
对象名称: GanttLink => 记录名称: GanttLink Name
接下来,创建所需字段。
- Source
创建一个 “Source” 字段。将数据类型选择为 “Text”。

点击 “Next” 按钮(接受默认设置),直到出现 “Save & New” 按钮。
- Target
创建一个 “Target” 字段。将数据类型选择为 “Text”。

点击 “Next” 按钮(接受默认设置),直到出现 “Save & New” 按钮。
- Type
创建一个 “Type” 字段。将数据类型选择为 “Text”。

点击 “Next” 按钮(接受默认设置),直到出现 “Save” 按钮。
最终它应看起来像这样:

第 5 步:创建一个 Lightning Web Component
要创建一个 Lightning Web Component,请运行以下命令:
$ sfdx lightning generate component --type lwc -n gantt -d force-app/main/default/lwc
target dir =
C:UsersUsersourcesalesforcegantt-salesforce-appforce-appmaindefaultlwc
create force-appmaindefaultlwcganttgantt.js
create force-appmaindefaultlwcganttgantt.html
create force-appmaindefaultlwcganttgantt.js-meta.xml
在 gantt.js-meta.xml 中将组件定义修改为在 Lightning App Builder 中进行暴露:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__AppPage">
<property name="height" label="Height" type="Integer" default="800" />
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
打开 gantt.html,在其中添加以下代码:
<template>
<div class="thegantt" lwc:dom="manual" style='width: 100%;'></div>
</template>
打开 gantt.js, 在其中添加以下代码:
/* eslint-disable guard-for-in */
/* eslint-disable no-undef */
import { LightningElement, api } from "lwc";
import { ShowToastEvent } from "lightning/platformShowToastEvent";
import { loadStyle, loadScript } from "lightning/platformResourceLoader";
import { createRecord, updateRecord, deleteRecord } from "lightning/uiRecordApi";
// 静态资源
import GanttFiles from "@salesforce/resourceUrl/dhtmlxgantt7111";
// 控制器
import getTasks from "@salesforce/apex/GanttData.getTasks";
function unwrap(fromSF) {
const data = fromSF.tasks.map((a) => ({
id: a.Id,
text: a.Name,
start_date: a.Start_Date__c,
duration: a.Duration__c,
parent: a.Parent__c,
progress: a.Progress__c,
type: a.Task_Type__c,
}));
const links = fromSF.links.map((a) => ({
id: a.Id,
source: a.Source__c,
target: a.Target__c,
type: a.Type__c
}));
return { data, links};
}
export default class GanttView extends LightningElement {
static delegatesFocus = true;
@api height;
ganttInitialized = false;
renderedCallback() {
if (this.ganttInitialized) {
return;
}
this.ganttInitialized = true;
Promise.all([
loadScript(this, GanttFiles + "/dhtmlxgantt.js"),
loadStyle(this, GanttFiles + "/dhtmlxgantt.css")
])
.then(() => {
this.initializeUI();
})
.catch((error) => {
this.dispatchEvent(
new ShowToastEvent({
title: "Error loading Gantt",
message: error.message,
variant: "error"
})
);
});
}
initializeUI() {
const root = this.template.querySelector(".thegantt");
root.style.height = this.height + "px";
// 使用 Enterprise 或 Ultimate 版本时,请取消注释以下行
//const gantt = window.Gantt.getGanttInstance();
gantt.templates.parse_date = (date) => new Date(date);
gantt.templates.format_date = (date) => date.toISOString();
gantt.init(root);
getTasks().then((d) => {
const chartData = unwrap(d);
gantt.parse({
tasks: chartData.data,
links: chartData.links
});
});
///↓↓↓ 将变更保存回 SF 后端 ↓↓↓
gantt.createDataProcessor({
task: {
create: (data) => {
console.log("createTask",data);
const insert = {
apiName: "GanttTask__c",
fields: {
Name: data.text,
Start_Date__c: data.start_date,
Duration__c: data.duration,
Parent__c: String(data.parent),
Progress__c: data.progress
}
};
gantt.config.readonly = true; // 在保存完成前禁止修改
// 以防修改
return createRecord(insert).then((res) => {
gantt.config.readonly = false;
return { tid: res.id, ...res };
});
},
update: (data, id) => {
console.log("updateTask",data);
const update = {
fields: {
Id: id,
Name: data.text,
Start_Date__c: data.start_date,
Duration__c: data.duration,
Parent__c: String(data.parent),
Progress__c: data.progress
}
};
return updateRecord(update).then(() => ({}));
},
delete: (id) => {
return deleteRecord(id).then(() => ({}));
}
},
link: {
create: (data) => {
const insert = {
apiName: "GanttLink__c",
fields: {
Source__c: data.source,
Target__c: data.target,
Type__c: data.type
}
};
return createRecord(insert).then((res) => {
return { tid: res.id };
});
},
update: (data, id) => {
const update = {
apiName: "GanttLink__c",
fields: {
Id: id,
Source__c: data.source,
Target__c: data.target,
Type__c: data.type
}
};
return updateRecord(update).then(() => ({}));
},
delete: (id) => {
return deleteRecord(id).then(() => ({}));
}
}
});
}
}
第 6 步:创建 Apex 类
下一步是创建一个类,用于在 Lightning 组件和我们的数据模型之间建立交互。
$ sfdx apex generate class -n GanttData -d force-app/main/default/classes
target dir =
C:UsersUsersalesforcegantt-salesforce-appforce-appmaindefaultclasses
create force-appmaindefaultclassesGanttData.cls
create force-appmaindefaultclassesGanttData.cls-meta.xml
创建后,打开 GanttData.cls 并将以下代码加入其中:
public with sharing class GanttData {
@RemoteAction
@AuraEnabled(cacheable="true)"
public static Map<String, Object> getTasks() {
// using SOQL 获取记录
List<GanttTask__c> Tasks = new List<GanttTask__c>();
Tasks = [SELECT Id, Name, Start_Date__c, Duration__c,
Parent__c FROM GanttTask__c];
List<GanttLink__c> Links = new List<GanttLink__c>();
Links = [SELECT Id, Type__c, Source__c, Target__c FROM GanttLink__c];
Map<String, Object> result = new Map<String, Object>{
'tasks' => Tasks, 'links' => Links };
return result;
}
}
将源代码从 Scratch Org 拉取到你的项目中
$ sfdx project retrieve start
然后将源代码推送到 Scratch Org
$ sfdx project deploy start
第 7 步:创建 Lightning 页面
打开 “Lightning App Builder”,创建一个新的 Lightning 页面。

选择 "App Page",然后输入页面名称和布局。



你应该能在新页面看到一个 Gantt 自定义组件。将其追加到任意区域并保存。

激活该页面。

保存更改。



打开应用程 序页面。如果一切顺利,你应该能在 Lightning 页面中看到一个简单的甘特图示例。

如果一切顺利,你应该能看到一个简单的甘特演示在 Lightning 页面中运行。

应用安全性
Gantt 并未提供任何防止应用程序受到各种威胁(如 SQL 注入、XSS 和 CSRF 攻击)的机制。确保应用安全的责任应由实现该应用的开发人员承担。请在相应文章中阅读详细信息:相应文章 。Salesforce 具有内置的安全性来保护你的数据和应用程序。你也可以实现你自己的安全方案,以符合你组织的结构和需求。欲了解更多信息,请参阅 Salesforce 安全指南。 在这里 你可以了解要确保安全需要做什么。
故障排除
如果 你已经完成上述步骤以实现与 Salesforce 的 Gantt 集成,但 Gantt 页面上不会渲染任务和链接,请查看 故障排除后端集成问题 文章。它描述了识别问题根源的方法。
下一步
现在你已经拥有一个完整可运行的甘特图。你可以在 GitHub 上查看完整代码,克隆或下载后用于你的项目。
你也可以查看 关于甘特图众多特性的指南 或 将 Gantt 与其他后端框架集成的教程。