使用Next.js与SurveyJS构建极速调查系统

本文详细介绍了如何利用Next.js框架和SurveyJS库构建高性能的调查表单系统,涵盖环境搭建、表单创建、动态页面生成和结果可视化分析,提供完整代码示例和实现步骤。

如何通过Next.js和SurveyJS构建极速调查系统

在本文中,我们将逐步介绍如何构建一个可用于创建新调查、分享调查并分析结果的网站。您的网站将极速运行且对SEO友好,充分利用Next.js的最新功能。借助SurveyJS,构建过程将变得灵活且简单,使处理调查变得毫不费力。

本文假设您了解React和Next.js的基础知识,但将引导您构建网站的每个组件和页面。您可以跟随文章获取所有代码,或跳至结尾并使用示例存储库。您还可以查看我为您部署的网站的最终版本。

Next.js是一个基于React的框架,可帮助您完全在React中构建全栈网站。Next.js处理所有捆绑,并为您提供强大的API来决定如何渲染每个页面,从而实现极速性能。在本文中,我们将确保所有页面都可以在构建时渲染。这意味着我们可以轻松暴露站点地图供Google索引您的网站,这对于确保SEO性能至关重要。

SurveyJS是一个开源表单管理工具,使您能够创建、分享和分析调查和表单。它们提供了一个React API,我们将使用它与Next.js一起创建调查管理系统。

设置Next.js

首先,让我们设置Next.js应用程序。使用Next.js快速入门很容易,因为它们提供了一个CLI工具,让您根据偏好创建基本应用程序。

要使用该工具,您需要确保已安装npx,然后运行以下命令:

1
npx create-next-app@latest

运行create-next-app命令后,它将询问您一系列关于要创建的项目的问题。大多数问题完全基于个人偏好,因此您可以随意回答。对于本文,我们将使用纯JavaScript(而不是TypeScript),并且还将使用Next.js中的新应用程序路由器而不是旧的文件路由器。

现在您已经设置了Next.js应用程序,您可以使用以下命令运行它:

1
yarn run dev

这将启动一个开发服务器,每当您更改文件时都会更新。现在,让我们保持运行,以便我们可以添加页面而无需每次重建。

设置SurveyJS

要设置SurveyJS,我们将需要安装所有不同的依赖项。我们将使用SurveyJS的所有不同部分,包括表单创建器、表单显示和结果包,因此我们需要确保安装它们所有。

要安装包,请确保运行以下安装命令:

1
yarn add survey-analytics survey-core survey-creator-core survey-creator-react survey-react-ui

设置表单创建器

首先,让我们从添加表单创建器页面开始。我将使我的页面在/creator可用,因此为此,我在/creator/page.js创建一个文件。

创建器不需要任何服务器端数据来渲染,这意味着我们的页面组件非常简单;它只是渲染我们的Creator组件,我将在后面概述。它看起来像这样:

1
2
3
4
5
6
7
export const metadata = {
  title: "Survey Creator",
};

export default function Page() {
  return <Creator />;
}

在上面的代码中,您可以看到我导出了页面和元数据对象。元数据对象随后将由Next.js用于SEO元标签。对于此页面,我们始终希望使用相同的字符串,因此我们只导出一个对象。

Creator组件是我们实际使用SurveyJS API的地方。让我们看一下该组件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
"use client";

import { useEffect, useState } from "react";
import { SurveyCreatorComponent, SurveyCreator } from "survey-creator-react";

export default function Creator() {
  let [creator, setCreator] = useState();

  useEffect(() => {
    const newCreator = new SurveyCreator({
      showLogicTab: true,
      showTranslationTab: true,
    });
    setCreator(newCreator);
  }, []);

  return <div>{creator && <SurveyCreatorComponent creator={creator} />}</div>;
}

您会注意到的第一件事是我们在该组件中使用use client指令。这是因为SurveyJS组件并非设计为服务器组件。不过不用担心;它们仍将在发送到客户端之前在服务器上首先渲染。

接下来您会看到我们运行一个带有空依赖数组的useEffect。这意味着该函数将运行一次并创建SurveyCreator。您可以看到在这一点上,我们可以根据要启用的功能将任何选项传递给创建器。

我们需要做的就是渲染SurveyCreatorComponent并将创建器对象传递给它。我们可选地渲染它,以便在设置创建器之前不会破坏。

您的开发服务器应该一直在重新加载,因此如果您现在访问/creator,您将能够访问创建器并使用所有功能,如下面的屏幕截图所示。

创建页面以查看表单

接下来,我们想创建一个页面来查看我们构建的表单。一旦您在设计器中创建了表单,输出将是一个JSON对象,其中包含您的问题以及在构建调查时设置的偏好,包括任何逻辑或样式。

对于我们的表单页面,我们希望使用动态设置,以便我们可以渲染任意数量的表单页面,而无需为每个新表单创建新文件。我们通过使用Next.js动态路由来实现这一点。要创建动态路由,我们需要在/app/form/[slug]/page.js创建一个新文件,这将为我们所有的表单在/form/form-slug提供一个单独的页面。

在我们的新文件中,我们必须创建一些函数来支持Next.js创建我们的页面。首先,让我们从generateStaticParams开始,我们可以使用它来告诉Next.js我们想要生成哪些页面。下面您可以看到该函数的内容:

1
2
3
export async function generateStaticParams() {
  return surveys.map((x) => ({ slug: x.slug }));
}

对于这个项目,我们设置了一个文件,该文件导出一个调查列表(包含一个slug)和一个调查(由调查设计器提供的对象)。如果我们想添加一个新调查,我们只需要向我们的调查数组添加另一个条目。我们的generateStaticParams函数需要导出一个slug列表,Next.js随后将在构建时使用它来渲染我们的页面。对我们来说,这真的很简单;我们只需要映射我们的调查数组以适应格式:

1
2
3
4
5
6
7
8
export async function generateMetadata({ params }) {
  const survey = surveys.find((x) => x.slug === params.slug);

  return {
    title: survey.survey.title,
    description: survey.survey.description,
  };
}

我们将要看的下一个函数是generateMetadata。它接受我们刚刚定义的静态参数函数的参数,然后返回我们的标题和描述,这些用于我们网页上的元数据。正如您在上面看到的,我们的函数根据给定的slug找到正确的调查对象。然后我们可以使用在创建调查时编写的相同标题和描述。

我们需要在page.js文件中定义的最后一件事是React页面本身。我们表单页面的页面组件也非常简单。它再次找到调查对象,然后将其传递给SurveyComponent:

1
2
3
4
5
6
7
8
9
export default function Page({ params: { slug } }) {
  const survey = surveys.find((x) => x.slug === slug);

  return (
    <div>
      <SurveyComponent surveyData={survey.survey} />
    </div>
  );
}

然后必须单独定义SurveyComponent。看一下该组件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"use client";

import { useCallback } from "react";
import { Model } from "survey-core";
import { Survey } from "survey-react-ui";

export default function SurveyComponent({ surveyData }) {
  const model = new Model(surveyData);

  const alertResults = useCallback(async (sender) => {
    fetch("/api/submit", {
      method: "POST",
      headers: {
        "Content-Type": "application/json;charset=UTF-8",
      },
      body: JSON.stringify({ result: sender.data }),
    });
  }, []);

  model.onComplete.add(alertResults);

  return <Survey model={model} />;
}

再次,您会注意到我们有use client指令以确保Next.js知道它不是服务器组件。然后我们使用SurveyJS创建一个模型并将其传递到SurveyJS Survey组件中。在此之前,您会注意到我们设置了一个onComplete函数。在我们的例子中,该函数只是将原始数据发送到/api/submit,然后可以在那里处理。

您可以使用Next.js创建API端点。在我们的例子中,我们可以通过在/api/submit/route.js创建一个文件并在其中放置一个POST函数来实现,如下所示:

1
2
3
4
5
6
7
export async function POST(request) {
  const res = await request.json();

  console.log(res);

  return Response.json({ message: "Done" });
}

在我们的例子中,POST函数非常简单:它获取发送的对象,然后将其记录到控制台并响应一条消息。这是您希望将结果保存到数据库(如果您有的话)的地方。您可能还选择进一步验证结果并返回一个结果以在前端显示。在这一点上,完全由您决定如何处理数据。

创建页面以查看结果

现在我们已经设置了创建和显示表单的方法,我们需要设置一种方法来查看我们从表单收集的结果。显然,查看结果的一种方法是直接查看数据库,但这不会让您了解调查中出现的趋势。如果我们想识别趋势,我们可以使用surveyjs-analytics包。

对于这个项目,我创建了一些假结果数据,以便我们可以创建结果仪表板。我向我们之前使用的每个调查对象添加了一个结果数组。每个结果看起来像这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "nps-score": 9,
  "disappointing-experience": [
    "The service is great, i highly recommend you use it.",
  ],
  "improvements-required": [
    "The service is great, i highly recommend you use it.",
  ],
  "promoter-features": ["ui"],
  rebuy: [true, false],
}

正如您所看到的,每个结果只是一个对象,其中问题ID作为键,答案作为值。这正是当表单提交时我们从onComplete函数得到的内容。

首先,我们想创建一个新的动态页面,因为我们想为每个不同的表单创建一个新的网页,以便我们可以专门显示该表单的结果。对于此页面,我们想在/results/[slug]/page.js创建一个新文件。

再次,我们想定义一个generateMetadata和一个generateStaticParams,就像我们显示表单时所做的那样。在我们的generateMetadata函数中,我们对标题进行了轻微调整,以便清楚地表明我们正在查看结果而不是表单本身。这次唯一的区别是,在我们的generateStaticParams内部,我们过滤了一些没有结果的表单,因此我们不会为没有任何结果的表单生成页面。我们的generateStaticParams函数最终看起来像这样:

1
2
3
4
5
export async function generateStaticParams() {
  return surveys
    .filter((x) => x.results.length > 0)
    .map((x) => ({ slug: x.slug }));
}

再次,我们还想导出一个Page组件。我们的页面组件与上一节中的页面组件相同,只是我们渲染组件Results。但我们仍然进行查找以获取正确的调查数据并将其传递给组件。

我们的Results组件加载所有必需的包,然后将它们渲染到页面上。它需要一些useEffect钩子来设置,整个组件看起来像这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
"use client";

import { useEffect } from "react";
import { Model } from "survey-core";

export default function Results({ surveyData }) {
  useEffect(() => {
    (async () => {
      const survey = new Model(surveyData.survey);

      const { VisualizationPanel } = await import("survey-analytics");

      const currentPanel = new VisualizationPanel(
        survey.getAllQuestions(),
        surveyData.results,
        {
          allowHideQuestions: false,
        }
      );

      currentPanel.render("surveyVizPanel");

      return () => {
        const panelElement = document.getElementById("surveyVizPanel");

        if (panelElement) {
          panelElement.innerHTML = "";
        }
      };
    })();
  }, [surveyData]);

  return (
    <div>
      <div id="surveyVizPanel" />
    </div>
  );
}

正如您所看到的,我们再次以use client指令开始,原因与之前相同。组件以一个useEffect开始,该useEffect用于设置显示所有图表的面板。它首先使用定义调查本身的surveyData对象创建一个Model。这让结果包知道要显示哪些图表,因为它可以理解每个问题。

useEffect接下来要做的是加载survey-analytics包。我们通过动态导入来做到这一点,因此它不会在构建时加载。这种方法防止了由包中客户端特定代码引起的构建时错误。

获取所需的包后,我们使用所有问题设置可视化对象,然后我们可以给它一个所有提交的列表,让它遍历并从中创建图表。在这一点上,您可以使用提供的选项配置您的可视化。之后,您所要做的就是让面板对象知道在DOM中使用哪个ID进行渲染,在我们的例子中是surveyVizPanel,我们在下面渲染。最后,我们必须确保为我们的钩子提供一个清理函数,以便它在完成后清除元素。

您会注意到我们只将surveyData传递到依赖数组中,以便只有在输入数据更改时才重新渲染所有图表,如果我们在不同结果页面之间链接,可能会出现这种情况。

进一步工作

本文已经给了您足够的想法来开始将SurveyJS集成到您的Next.js应用程序中。要拥有一个功能齐全的系统,您需要研究添加某种身份验证系统,以便确保只有经过验证的用户才能访问系统的不同部分。

您还需要与某种数据源集成,既为创建者创建新表单,又从最终用户收集结果。所有这些添加在Next.js和SurveyJS中都非常简单。

结论

本指南向您展示了如何在Next.js中使用SurveyJS构建一个全面的调查管理系统。您开箱即用地获得Next.js的许多好处,因此尽管您可能没有编写那么多代码,但您会发现您创建的内容将扩展到任意多的表单,而没有任何麻烦。

感谢您花时间阅读本指南。正如我之前提到的,您可以在此处查看完整存储库,或者您可以在此处玩转系统的托管版本。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计