本教程将介绍如何将 dhtmlxGantt 集成到 Salesforce Lightning Web Component 中。
如果你使用的是其他技术,可以参考以下集成方案:
集成过程涉及使用 Salesforce CLI 创建 Lightning Web Component 并将其部署到 Salesforce 组织。为了获得更流畅的开发体验,建议在 Visual Studio Code 中安装 Salesforce Extension Pack。
完整源码已托管在 GitHub。
此外,还提供了视频教程,演示如何使用 Salesforce LWC 构建 Gantt 图表。
请确保已经安装了 Salesforce CLI。如未安装,可参考本指南进行安装。
如果还没有开发者账号,可以注册一个免费的开发者账号。具体操作可参考本指南。
在 Salesforce 中,使用左侧搜索栏找到并选择 Dev Hub:
在打开的设置页面中,启用 Dev Hub 功能:
接下来,为 Salesforce DX 项目创建一个目录:
$ mkdir ~/salesforce
使用 CLI 生成 Salesforce DX 项目:
$ cd ~/salesforce
$ sfdx project generate -n gantt-salesforce-app
target dir = C:\Users\User\salesforce
create gantt-salesforce-app\config\project-scratch-def.json
create gantt-salesforce-app\README.md
create gantt-salesforce-app\sfdx-project.json
create gantt-salesforce-app\.husky\pre-commit
create gantt-salesforce-app\.vscode\extensions.json
create gantt-salesforce-app\.vscode\launch.json
create gantt-salesforce-app\.vscode\settings.json
create gantt-salesforce-app\force-app\main\default\lwc\.eslintrc.json
create gantt-salesforce-app\force-app\main\default\aura\.eslintrc.json
create gantt-salesforce-app\scripts\soql\account.soql
create gantt-salesforce-app\scripts\apex\hello.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-app\jest.config.js
create gantt-salesforce-app\package.json
进入新创建的项目文件夹:
$ cd gantt-salesforce-app
使用 Web Server Flow 授权 Org:
$ sfdx org login web -d
Successfully authorized ... with org ID ...
然后,在项目的配置文件(sfdx-project.json)中,将 "sfdcLoginUrl" 参数设置为组织的 "My Domain URL"。该 URL 可在 "My Domain" 设置页面找到。例如:
gantt-salesforce-app/sfdx-project.json
"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.
要使用该库,需要将其作为静态资源上传到 Salesforce。打开你的 scratch org:
$ sfdx org open
进入 "Static Resources" 标签页,点击 "New" 按钮:
填写清晰的名称(如 "dhtmlxgantt7111"),上传包含库文件(dhtmlxgantt.js 和 dhtmlxgantt.css)的 ZIP 压缩包,并将 Cache Control 设置为 "Public" 以提升性能。然后保存更改。
现在,dhtmlxGantt 库已经可以在 Salesforce 内部使用。
dhtmlxGantt 的主要组件是 Tasks 和 Links。一个实用的处理方式是将它们的属性以 JSON 格式存储在 Salesforce 中。首先为 Tasks 和 Links 创建自定义对象。在 Object Manager 中,选择 "Create",然后选择 "Custom Object":
为任务对象命名,例如 GanttTask 或 GanttTasks。
请确保记录名称与对象名称一致,例如:
Object Name: GanttTask => Record Name: GanttTask Name
保存新对象。
随后,打开 "Fields & Relationships" 标签页,点击 "New" 添加字段:
选择 "Number" 作为数据类型,然后继续。
将字段命名为 "Duration"。该字段用于存储 JSON 序列化的 Task 属性。持续点击 "Next" 直到出现 "Save & New" 按钮。
接受默认设置,点击 "Next" 直到可以保存或添加新字段。
创建 "Parent" 字段,数据类型选择 "Text"。
继续点击 "Next" 直到可用 "Save & New" 按钮。
添加 "Progress" 字段,数据类型选择 "Number"。
继续点击 "Next" 直到可以保存或添加新字段。
创建 "Start Date" 字段,数据类型选择 "Date/Time"。
点击默认选项,直到出现 "Save" 按钮。
最终,你的对象字段应如下所示:
首先,打开对象管理器,选择“创建”,然后选择“自定义对象”:
将该链接对象命名为 GanttLink/GanttLinks。
请确保记录名称与对象名称相对应,例如:
对象名称:GanttLink => 记录名称:GanttLink Name
继续创建所需的字段。
添加一个名为“Source”的字段,并选择“文本”作为数据类型。
点击“下一步”(保持默认设置),直到出现“保存并新建”按钮。
添加一个名为“Target”的字段,数据类型同样选择“文本”。
点击“下一步”(接受默认设置),直到可以看到“保存并新建”按钮。
添加一个名为“Type”的字段,数据类型同样选择“文本”。
点击“下一步”(接受默认设置),直到出现“保存”按钮。
最后,页面应如下所示:
要生成 Lightning Web 组件,运行以下命令:
$ sfdx lightning generate component --type lwc -n gantt -d force-app/main/default/lwc
target dir =
C:\Users\User\source\salesforce\gantt-salesforce-app\force-app\main\default\lwc
create force-app\main\default\lwc\gantt\gantt.js
create force-app\main\default\lwc\gantt\gantt.html
create force-app\main\default\lwc\gantt\gantt.js-meta.xml
在 gantt.js-meta.xml 中更新组件定义,以便在 Lightning App Builder 中暴露该组件:
force-app/main/default/lwc/gantt/gantt.js-meta.xml
<?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,插入以下代码:
force-app/main/default/lwc/gantt/gantt.html
<template>
<div class="thegantt" lwc:dom="manual" style='width: 100%;'></div>
</template>
在 gantt.js 中,添加如下代码:
force-app/main/default/lwc/gantt/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";
// Static resources
import GanttFiles from "@salesforce/resourceUrl/dhtmlxgantt7111";
// Controllers
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";
//uncomment the following line if you use the Enterprise or Ultimate version
//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
});
});
///↓↓↓ saving changes back to SF backend ↓↓↓
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; // suppress changes
// until saving is complete
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(() => ({}));
}
}
});
}
}
接下来,创建一个类,用于处理 Lightning 组件与数据模型之间的通信。
$ sfdx apex generate class -n GanttData -d force-app/main/default/classes
target dir =
C:\Users\User\salesforce\gantt-salesforce-app\force-app\main\default\classes
create force-app\main\default\classes\GanttData.cls
create force-app\main\default\classes\GanttData.cls-meta.xml
创建类后,打开 GanttData.cls 并添加以下代码:
force-app/main/default/classes/GanttData.cls
public with sharing class GanttData {
@RemoteAction
@AuraEnabled(cacheable=true)
public static Map<String, Object> getTasks() {
// fetching the Records via 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
启动“Lightning App Builder”并创建一个新的 Lightning 页面。
选择“App Page”,并填写页面名称和布局。
Gantt 自定义组件现在应可用于新页面。将其添加到任意分区并保存。
激活该页面。
保存你的更改。
打开应用页面。你可以通过在应用启动器中输入 Gantt 来访问它。
如果一切配置正确,Lightning 页面上将显示一个简单的甘特图演示。
Gantt 本身不提供针对 SQL 注入、XSS 或 CSRF 攻击等威胁的内置防护。确保应用安全是开发者的责任。更多详情请参阅相关文档。Salesforce 提供了强大的安全功能来保护你的数据和应用。你也可以根据自己组织的结构和需求定制安全策略。如需进一步指导,请查阅 Salesforce Security Guide。关于 Lightning 组件安全的更多信息,请参阅此处。
如果你已完成所有步骤,但页面上的甘特图未显示任务和链接,请参阅 Troubleshooting Backend Integration Issues 文章。该文档提供了诊断和解决常见问题的方法。
完成甘特图配置后,你可以在 GitHub 上查看完整代码,支持克隆或下载,用于你的项目。
此外,你还可以查阅涵盖各种甘特功能的指南或与其他后端框架集成甘特的教程。
Back to top