使用 ToolJet 构建 Ed-Tech 销售 CRM

2025-06-10

使用 ToolJet 构建 Ed-Tech 销售 CRM

介绍

有效的客户关系管理 (CRM) 系统可以帮助教育科技公司简化销售流程、维护客户信息并增强客户互动。

在本教程中,我们将指导您完成使用ToolJetToolJet 数据库构建 Ed-tech 销售 CRM 的过程

以下是我们将在 ToolJet 中构建的 CRM 的快速预览。

图片描述

先决条件:

设置 ToolJet 数据库

本节将教您如何为我们的 CRM 应用设置数据库。我们将使用ToolJet 数据库来构建此应用。

登录您的 ToolJet 帐户,然后点击左侧边栏中的 ToolJet 数据库图标。我们将创建两个表,一个用于记录sales_revenue 数据,另一个用于记录sales_executives 数据。让我们先从创建数据库表开始。

创建具有以下列的新表并将其重命名为 sales_revenue

  • id(主键/自动生成)
  • 课程(varchar)
  • 已售课程数量(整数)
  • 折扣(varchar)
  • 客户姓名(整数)
  • 客户电子邮件 (varchar)
  • 客户国家 (varchar)
  • 客户年龄组(整数)
  • 收入(整数)
  • se_name (varchar)
  • 职业(varchar)
  • 销售日期 (varchar)

创建具有以下列的新表并将其重命名为 sales_executives

  • id(主键/自动生成)
  • 名称(varchar)
  • 电子邮件(varchar)
  • 电话 (varchar)

我们建议在表中添加一些虚拟数据,以便我们在开始应用程序开发过程时可以使用这些数据。

UI 开发

完成数据库设置后,点击侧边栏中的“应用程序”,创建一个名为“Ed-Tech Sales CRM”的新应用程序。成功创建应用程序后,您将进入App-Bulider页面。

现在我们可以拖放 ToolJet 的预构建组件来快速构建应用程序的 UI。

图片描述

以下是我们将要构建的应用程序的结构:

  • 标题
  • 标签
    • 概述
    • 销售主管
    • 收入

让我们开始构建标题

  •  从右侧 组件库中拖放一个Container组件到画布上 ,并将其重命名为header。容器用于在 App-Builder 中对相关组件进行分组。
  • 调整其大小并将其颜色更改为#2E425A,边框半径更改为8。
  • 在 Container 组件内,添加两个Text组件并将其重命名为 brandNameCRMTitle
  • brandName组件中,将数据更改为TOOLJET,并将颜色更改为白色,字体大小更改为 20,字体粗细更改为更粗,字母间距更改为 6。
  • CRMTitle组件中,将数据更改为Ed-Tech Sales CRM,并将颜色更改为白色,字体大小更改为 18。

图片描述

随着应用程序的增长,重命名组件会很有用,允许在应用程序的各个部分轻松引用与组件相关的值。

  • 拖放选项卡组件并将选项卡名称更改为概览销售主管收入

  • 在“收入”选项卡中,添加一个组件,它将包含数据库中sales_revenue表中的数据。

  • 现在添加一个Modal组件,将其放置在表格上方,并将其重命名为saleDetails。在属性中,将标题更改为Sale Details,将触发按钮标签更改为Add sale,并将按钮颜色更改为原色(#2E425A)。

图片描述

  • 现在我们需要在这个模态框中添加输入字段。这里我们将模态框分为两部分,首先是客户详情收入详情

  • 在客户详细信息,添加用于输入姓名电子邮件的文本输入组件、用于输入国家课程销售主管姓名的下拉组件、用于输入年龄组职业的单选按钮组件以及用于输入销售日期的日期组件

  • 对于国家/地区下拉菜单,您可以在选项值和标签中添加以下数据:

{{ ["Argentina", "Australia", "Brazil", "Canada", "China", "Egypt", "France", "Germany", "Greece", "India", "Indonesia", "Italy", "Japan", "Mexico", "Netherlands", "New Zealand", "Russia", "Saudi Arabia", "South Africa", "South Korea", "Spain", "Switzerland", "Thailand", "United Kingdom", "United States"]
}}
Enter fullscreen mode Exit fullscreen mode
  • 对于课程下拉菜单,您可以在选项值和标签中添加以下数据:
{{ ["Marketing Management", "Data Science Fundamentals", "Financial Accounting", "Introduction to Psychology", "Business Ethics", "Digital Marketing Strategy", "Creative Writing Workshop", "Computer Programming Basics", "Public Speaking Mastery", "Introduction to Economics", "Full Stack Web Development"]
}}
Enter fullscreen mode Exit fullscreen mode
  • 对于销售主管下拉菜单,我们将从数据库中的sales_executives表中提取数据。

  • 这是salesDetails模态 UI的快速预览,您可以根据需要更改设计。

图片描述

  • salesDetails模态框中,如您所见,我们拥有动态 UI元素,这些元素将根据用户在“已售课程”和“已提供折扣”中选择的内容,显示“不含折扣的收入”“总收入”的值。这两个文本组件的十六进制颜色代码分别为 #4A90E2#9013FE

  • 为了构建这些动态文本元素,我们需要在“出售的课程”“提供的折扣”上添加更改事件处理程序

  • 我们将运行一个脚本来计算这两项收入,然后将这些详细信息填充到文本组件中。

  • 打开查询管理器并添加一个 Javascript 查询。将其命名为updateRevenue

  • 将以下代码添加到此查询中。这将返回我们在 UI 中分别可以在“不含折扣的收入”“总收入”中使用的值。确保将数字输入组件重命名为“courses”,将单选按钮组件重命名为“discount”

const revenue = components.courses.value * 2000 * (100 - components.discount.value) * 0.01
const revenueWithoutDiscount = components.courses.value * 2000

return {revenue, revenueWithoutDiscount}
Enter fullscreen mode Exit fullscreen mode

ToolJet 中的事件用于运行查询、显示警报以及基于按钮单击或查询完成等触发器的其他功能

  • 更新无折扣收入文本组件的数据属性{{queries.updateRevenue.data.revenueWithoutDiscount ? queries.updateRevenue.data.revenueWithoutDiscount : 0}}

  • 更新“总收入生成”文本组件的数据属性:{{queries.updateRevenue.data.revenue ? queries.updateRevenue.data.revenue : 0}}

图片描述

  • 完成这些更新后,只需分别课程销售数量输入和折扣提供单选按钮输入中添加更改和选择事件处理程序,并添加updateRevenue查询。

  • 销售主管选项卡中,我们还将添加一个模式和一个表格,因此请将这两个组件拖放到选项卡中。

  • 将模态窗口尺寸改为小,高度改为 300px。同时将触发按钮的颜色改为#2E425A

  • 现在在模态框中添加三个文本组件,分别为NameEmailPhone,以及一个提交按钮。将提交按钮的颜色更改为#2E425A

图片描述

  • “概览”选项卡中,添加三个“统计”#F28585组件。隐藏次要值,并将三个组件的主颜色更改为。

  • 将主要值标签设置为“总收入”“课程总数”“客户总数”。暂时保留主要值默认值。我们稍后添加数据后会更新。

  • 现在从组件库中添加七个图表组件。将其中五个设置为饼图,将其余两个设置为条形图。

  • 将所有图表的背景颜色更改为#F1F4FC。对于两个条形图,将标记颜色设置为#2A77B4

图片描述

图片描述

我们已经完成了 UI 的构建,现在让我们快速创建查询并向我们的应用程序添加功能。

数据获取

使用 ToolJet,我们可以轻松地将 UI 元素连接到数据源,并通过在查询管理器中创建查询来获取数据。

我们将为“收入”“销售主管”“概览”选项卡添加查询。我们将从“销售主管”选项卡开始,因为我们在“收入”选项卡中添加销售额时需要用到这些数据。

1. 销售主管标签

这里,我们需要两个查询,一个是将数据添加到数据库,一个是显示表中的数据。

  • 打开底部的查询管理器,点击“添加”,然后点击ToolJet 数据库。将查询重命名为getSalesExecutives

  • 选择表名称为sales_executives,操作为List rows。在设置中,打开“在应用程序加载时运行此查询?”切换按钮,以便在每次应用重新加载时运行此查询。

  • 完成此操作后,您可以单击“预览”来检查数据。

  • 让我们将此查询链接到表。单击表并添加{{queries.getSalesExecutives.data}}数据。您将在用户界面中看到来自数据库的数据。

现在我们需要添加另一个查询来从模态组件中添加 sales_executives 中的数据。

  • 在查询管理器中点击“添加”,然后点击ToolJet 数据库。将查询重命名为addSalesExecutive

  • 选择表名为sales_executives,操作为Create row 。然后在列中选择nameemailphone 。

  • 现在我们需要将 UI 中的数据添加到按键输入的这些列中。在按键输入的相应列中添加{{components.seName.value}}{{components.seEmail.value}}、 。{{components.sePhone.value}}

  • 这里的seNameseEmailsePhone是组件名称。请确保在将它们添加到查询之前重命名它们。

  • 打开模态框并选择提交按钮。在属性中,点击“新建事件处理程序”并添加点击事件,选择“运行查询”作为操作,并选择“addSalesExecutive”查询。

  • 最后我们需要添加的是查询中的事件处理程序。您可以添加以下三个事件:关闭模态框、成功提示和运行getSalesExecutives查询。

  • 尝试在输入组件中添加数据并提交。您将看到该数据填充到表中。

2. 收入标签

在此选项卡中,我们还需要两个查询,一个是将数据添加到数据库,一个是显示表中的数据。

  • 打开底部的查询管理器,点击“添加”,然后点击“运行 Javascript 代码”。将查询重命名为getRevenueDetails

  • 选择表名称为sale_revenue,操作为List rows。在设置中,打开在应用程序加载时运行此查询?”切换开关,以便在每次应用程序重新加载时运行此查询。

  • 完成此操作后,您可以单击“预览”来检查数据。

  • 让我们将此查询链接到表。单击表并添加{{queries.getRevenueDetails.data}}数据。您将在用户界面中看到来自数据库的数据。

现在我们需要添加另一个查询来从 Modal 组件添加 sale_revenue 中的数据。

  • 在查询管理器中单击“添加”,然后单击ToolJet 数据库。将查询重命名为addRevenueDetails

  • 选择表名称为sale_revenue,操作为Create row

  • 添加 和 列,然后将 UI 中的数据添加到按键输入中的这些列。根据您为所有输入组件添加的名称,添加 和 以及模态框中的所有其他项目{{components.name.value}}{{components.email.value}}

  • 打开模态框并选择提交按钮。在属性中,点击“新建事件处理程序”并添加点击事件,选择“运行查询”作为操作,并选择“addRevenueDetails”查询。

  • 您可以将这三个事件 - 关闭模式、成功提示和运行getRevenueDetails添加到您的查询中。

  • 尝试在输入中添加数据并提交。您将看到数据填充到表中。

3. 概览选项卡

在此选项卡中,我们将使用 Javascript 计算所有图表和统计组件中要添加的所有数据。

  • 在底部的查询管理器中打开,单击“添加”,然后单击“运行 Javascript 代码”。将其重命名为“analytics”

  • 您可以粘贴以下代码,该代码可以计算可以放入所有图表和统计组件中的所有数据。

await queries.getRevenueDetails.run(); 

let data = queries.getRevenueDetails.getData(); 

const totalRevenue = data.reduce((acc, obj) => acc + obj.revenue, 0);
const totalCourseSold = data.reduce((acc, obj) => acc + obj.number_of_courses_sold, 0);
const totalCustomers = Array.from(new Set(data.map(obj => obj.customer_email))).length


const countryCounts = data.reduce((counts, obj) => {
  counts[obj.customer_country] = (counts[obj.customer_country] || 0) + 1;
  return counts;
}, {});

const customerCountryData = Object.keys(countryCounts).map(state => ({ x: state, y: countryCounts[state] }));

const ageRangeCounts = data.reduce((counts, obj) => {
  counts[obj.customer_age_group] = (counts[obj.customer_age_group] || 0) + 1;
  return counts;
}, {});

const ageRangeData = Object.keys(ageRangeCounts).map(state => ({ x: state, y: ageRangeCounts[state] }));

const discountCounts = data.reduce((counts, obj) => {
  counts[obj.discount] = (counts[obj.discount] || 0) + 1;
  return counts;
}, {});

const discountData = Object.keys(discountCounts).map(state => ({ x: state, y: discountCounts[state] }));

const professionCounts = data.reduce((counts, obj) => {
  counts[obj.profession] = (counts[obj.profession] || 0) + 1;
  return counts;
}, {});

const professionData = Object.keys(professionCounts).map(state => ({ x: state, y: professionCounts[state] }));


const courseCounts = data.reduce((counts, obj) => {
  if (obj.course) {
    counts[obj.course] = (counts[obj.course] || 0) + 1;
  }
  return counts;
}, {});


const courseData = Object.keys(courseCounts).map(course => ({ x: course, y: courseCounts[course] }));

const revenueByExecutive = data.reduce((acc, obj) => {
  if (obj.se_name) {
    acc[obj.se_name] = (acc[obj.se_name] || 0) + (obj.revenue || 0);
  }
  return acc;
}, {});


const teamData = Object.keys(revenueByExecutive).map(executive => ({ x: executive, y: revenueByExecutive[executive] }));

// Aggregate revenue by year
const revenueByYear = data.reduce((acc, entry) => {
    const year = parseInt(entry.sale_date.split('/')[2]);
    if (!acc[year]) {
        acc[year] = 0;
    }
    acc[year] += entry.revenue;
    return acc;
}, {});

// Format the data
const yearRevenueData = Object.entries(revenueByYear).map(([year, revenue]) => {
    return {
        y: revenue.toString(),
        x: parseInt(year)
    };
});

// Sort the data by year (y) in increasing order
yearRevenueData.sort((a, b) => a.y - b.y);



return {totalRevenue, totalCourseSold, totalCustomers, courseData, teamData, customerCountryData, ageRangeData, discountData, professionData, yearRevenueData};
Enter fullscreen mode Exit fullscreen mode

您可以预览并检查数据。现在我们将把查询连接到此选项卡中的所有组件。

  • 选择总收入组件并添加{{queries.analytics.data.totalRevenue}}主要值。

  • 选择“总课程”组件并添加{{queries.analytics.data.totalCourseSold}}“主要值”。

  • 选择“客户总数”组件并添加{{queries.analytics.data.totalCustomers}}主要值。

  • 选择客户位置图表组件并添加{{queries.analytics.data.customerCountryData}}图表数据。

  • 选择课程图表组件并添加{{queries.analytics.data.courseData}}图表数据。

  • 选择年龄范围图表组件并添加{{queries.analytics.data.ageRangeData}}图表数据。

  • 选择团队收入图表组件并添加{{queries.analytics.data.teamData}}图表数据。

  • 选择职业图表组件并添加{{queries.analytics.data.professionData}}图表数据。

  • 选择折扣图表组件并添加{{queries.analytics.data.discountData}}图表数据。

  • 选择年份与收入图表组件并添加{{queries.analytics.data.yearRevenueData}}图表数据。

将所有查询连接到组件后,打开“在应用程序加载时运行此查询?”在设置中切换,以便在每次应用程序重新加载时运行此查询。

图片描述

结论

恭喜,您的教育科技销售 CRM 应用现已全面投入使用!通过利用 Tooljet 和 ToolJet 数据库,您已创建一款功能强大且高效的 CRM,以满足教育科技行业的独特需求。您的应用现在可以高效地管理潜在客户、跟踪销售活动并分析绩效指标,所有这些都在一个用户友好的界面中进行。

我们希望本指南对您有所帮助,并能激发您探索 ToolJet 的更多功能和集成。如需继续探索,请查看 ToolJet 官方文档或通过Slack 联系  我们进行咨询和解答疑问。

鏂囩珷鏉ユ簮锛�https://dev.to/tooljet/building-an-ed-tech-sales-crm-using-tooljet-3e2i
PREV
使用 Google Sheets 和 ToolJet 构建库存和订单管理应用程序
NEXT
使用 Gemini AI + ToolJet 构建 SQL 报告生成器 📊