首页  >  文章  >  web前端  >  使用 Svelte 和 Perseid 构建用户反馈表

使用 Svelte 和 Perseid 构建用户反馈表

Patricia Arquette
Patricia Arquette原创
2024-10-02 06:35:02145浏览

在本指南中,我们将逐步使用 @perseid/form 库构建动态用户反馈表单,这是 FormlySuperforms 的强大替代品。您将看到 @perseid/form 如何轻松管理表单状态、验证和条件渲染。我们将构建的表单将要求用户对服务进行评分并提供反馈。根据评级,它会显示“谢谢”消息或提示用户提供其他反馈。

?让我们开始吧!


第 1 步:设置表单配置

第一步是定义表单配置。此配置概述了表单的行为方式,包括字段、步骤以及它们之间的流程。在这里,我们将根据用户的评分使用 条件逻辑 创建评分和评论字段。我们还将定义积极和消极反馈的消息。

这是配置代码:

import { type Configuration } from "@perseid/form";

const formConfiguration: Configuration = {
  // Root step-the form will start from there.
  root: "feedback",
  // Callback triggered on form submission.
  onSubmit(data) {
    alert(`Submitting the following JSON: ${JSON.stringify(data)}`);
    return Promise.resolve();
  },
  // `fields` define the data model the form is going to deal with.
  // Expect the submitted data JSON to match this schema.
  fields: {
    rating: {
      type: "integer",
      required: true,
    },
    review: {
      type: "string",
      required: true,
      // Display this field only if condition is met...
      condition: (inputs) =>
        inputs.rating !== null && (inputs.rating as number) < 3,
    },
    // Type `null` means that the value of this field will not be included in submitted data.
    submit: {
      type: "null",
      submit: true,
    },
    message_good: {
      type: "null",
    },
    message_bad: {
      type: "null",
    },
  },
  // Now that fields are defined, you can organize them in a single or multiple steps,
  // depending on the UI you want to build!
  steps: {
    feedback: {
      fields: ["rating", "review", "submit"],
      // Whether to submit the form at the end of this step.
      submit: true,
      // Next step is conditionned to previous user inputs...
      nextStep: (inputs) =>
        (inputs.rating as number) < 3 ? "thanks_bad" : "thanks_good",
    },
    thanks_good: {
      fields: ["message_good"],
    },
    thanks_bad: {
      fields: ["message_bad"],
    },
  },
};

在此配置中:

  • 表单从反馈步骤开始。
  • 该表单包含两个字段:评级(必填)和评论(可选,除非评级低于 3)。
  • 根据评级,表单导航至“好”或“坏”反馈消息。
  • 提交表单后,提交的数据会触发一个简单的警报。

这里要掌握的重点是fields属性的作用。它定义了将提交的数据的结构,本质上充当数据模型。相比之下,steps 属性概述了表单的流程,确定如何将这些字段呈现给用户。


第 2 步:创建表单的 Svelte 组件

现在我们已经有了配置,是时候构建将呈现表单的实际 UI 了。使用@perseid/form/svelte,我们可以创建自定义字段组件来管理表单每个部分的用户交互。

这是核心 Svelte 组件:

<!-- The actual Svelte component, used to build the UI! -->
<script lang="ts" context="module">
import type { FormFieldProps } from "@perseid/form/svelte";
</script>

<script lang="ts">
export let path: FormFieldProps['path'];
export let type: FormFieldProps['type'];
export let value: FormFieldProps['value'];
export let Field: FormFieldProps['Field'];
export let error: FormFieldProps['error'];
export let status: FormFieldProps['status'];
export let engine: FormFieldProps['engine'];
export let fields: FormFieldProps['fields'];
export let isActive: FormFieldProps['isActive'];
export let activeStep: FormFieldProps['activeStep'];
export let isRequired: FormFieldProps['isRequired'];
export let setActiveStep: FormFieldProps['setActiveStep'];
export let useSubscription: FormFieldProps['useSubscription'];

let currentRating = 0;
$: currentValue = value as number;
$: fields, isActive, activeStep, setActiveStep, useSubscription, type, error, Field, isRequired;

const setCurrentRating = (newRating: number) => {
  currentRating = newRating;
};

const handleReviewChange = (event: Event) => {
  engine.userAction({ type: "input", path, data: (event.target as HTMLTextAreaElement).value })
};
</script>

 <!-- Display a different element depending on the field... -->

{#if path === 'thanks_good.1.message_good'}
  <div class="message">
    <h1>Thanks for the feedback ?</h1>
    <p>We are glad you enjoyed!</p>
  </div>
{:else if path === 'thanks_bad.1.message_bad'}
  <div class="message">
    <h1>We're sorry to hear that ?</h1>
    <p>We'll do better next time, promise!</p>
  </div>
{:else if path === 'feedback.0.review'}
  <div class={`review ${status === "error" ? "review--error" : ""}`}>
    <label for="#review">Could you tell us more?</label>
    <textarea
      id="review"
      on:change={handleReviewChange}
    />
  </div>
{:else if path === 'feedback.0.rating'}
  <!-- Depending on the field status, define some extra classes for styling... -->
  <div
    role="button"
    tabindex="0"
    class={`rating ${status === "error" ? "rating--error" : ""}`}
    on:mouseleave={() => {
      setCurrentRating(currentValue ?? 0);
    }}
  >
    <h1>How would you rate our service?</h1>
    {#each [1, 2, 3, 4, 5] as rating (rating)}
      <span
        role="button"
        tabindex="0"
        class={`rating__star ${currentRating >= rating ? "rating__star--active" : ""}`}
        on:mouseenter={() => {
          setCurrentRating(rating);
        }}
        on:keydown={() => {}}
        on:click={() => {
          // On click, notify the form engine about new user input.
          engine.userAction({ type: "input", path, data: rating });
        }}
    ></span>
    {/each}
  </div>
{:else}
  <!-- path === 'feedback.0.submit' -->
  <button
    class="submit"
    on:click={() => {
      engine.userAction({ type: "input", path, data: true });
    }}
  >
    Submit
  </button>
{/if}

这里,Field 组件使用 path 属性来决定渲染什么:

  • 一个评级组件,用户可以在其中选择星级。
  • 供用户提供额外反馈的文本区域。

根据评分显示的“谢谢”消息。该表单将根据用户输入动态调整其字段和步骤。

很酷,对吧?

Building a User Feedback Form with Svelte and Perseid


第 3 步:运行应用程序

现在我们的表单配置和组件已准备就绪,让我们将它们集成到基本的 Svelte 应用程序中。这是初始化和渲染表单的代码:

// Let's run the app!
// Creating Svelte root...
const container = document.querySelector("#root") as unknown as HTMLElement;
container.innerHTML = '';
new Form({
  props: {
    Field: Field,
    configuration: formConfiguration,
  },
  target: container,
});

此代码将表单安装到 DOM。 Form 组件连接我们的配置和 Field 组件,处理其他所有事情。

第四步:添加样式

好吧,我们有了应用程序逻辑,但是如果您现在运行代码,您会发现它有点......原始?

Building a User Feedback Form with Svelte and Perseid

所以,让我们通过添加一些样式和动画来修饰表单!下面是一个简单的样式表,使它更具吸引力:

// A few animations for fun...

@keyframes swipe-out {
  0% {
    opacity: 1;
    transform: translateX(0);
  }
  75% {
    opacity: 0;
    transform: translateX(-100%);
  }
  100% {
    opacity: 0;
    transform: translateX(-100%);
  }
}

@keyframes swipe-in-one {
  0% {
    opacity: 0;
    transform: translateX(100%);
  }
  75% {
    transform: translateX(0);
  }
  100% {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes swipe-in-two {
  0% {
    opacity: 0;
    transform: translateX(0);
  }
  75% {
    transform: translateX(-100%);
  }
  100% {
    opacity: 1;
    transform: translateX(-100%);
  }
}

@keyframes bubble-in {
  0% {
    transform: scale(0.5);
  }
  75% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}

@keyframes fade-in {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

// Some global basic styling...

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  display: grid;
  height: 100vh;
  color: #aaaaaa;
  align-items: center;
  font-family: "Helvetica", sans-serif;
}

// And form-specific styling.

.perseid-form {
  width: 100%;
  margin: auto;

  &__steps {
    display: flex;
    overflow: hidden;
  }

  &__step {
    min-width: 100%;
    padding: 1rem 3rem;
    animation: 500ms ease-in-out forwards swipe-out;

    &__fields {
      display: grid;
      row-gap: 2rem;
    }
  }

  &__step[class*="active"]:first-child {
    animation: 500ms ease-in-out forwards swipe-in-one;
  }
  &__step[class*="active"]:last-child:not(:first-child) {
    animation: 500ms ease-in-out forwards swipe-in-two;
  }
}

.submit {
  border: none;
  cursor: pointer;
  padding: 1rem 2rem;
  border-radius: 8px;
  color: #fefefe;
  font-size: 1.25rem;
  background: #46c0b0;
  justify-self: flex-end;
  transition: all 250ms ease-in-out;

  &:hover {
    background: #4cccbb;
  }
}

.rating {
  position: relative;
  padding: 0.25rem 0;

  &__star {
    cursor: pointer;
    display: inline-block;
    font-size: 2rem;
    min-width: 2rem;
    min-height: 2rem;

    &::after {
      content: "⚪️";
    }

    &--active {
      animation: 250ms ease-in-out forwards bubble-in;
      &::after {
        content: "?";
      }
    }
  }

  &[class*="error"] {
    &::after {
      left: 0;
      bottom: -1.5rem;
      color: #f13232;
      position: absolute;
      font-size: 0.75rem;
      content: "? This field is required";
      animation: 250ms ease-in-out forwards fade-in;
    }
  }
}

.review {
  display: grid;
  row-gap: 1rem;
  position: relative;
  animation: 250ms ease-in-out forwards fade-in;

  label {
    font-size: 1.25rem;
  }

  textarea {
    resize: none;
    min-height: 5rem;
    border-radius: 8px;
    border: 1px solid #46c0b0;
    transition: all 250ms ease-in-out;
  }

  &[class*="error"] {
    &::after {
      left: 0;
      bottom: -1.5rem;
      color: #f13232;
      position: absolute;
      font-size: 0.75rem;
      content: "? This field is required";
      animation: 250ms ease-in-out forwards fade-in;
    }
  }
}

@media screen and (min-width: 30rem) {
  .perseid-form {
    max-width: 30rem;
  }
}

瞧?


结论

恭喜! ?您刚刚使用 Perseid 和 Svelte 构建了一个动态用户反馈表单。

在本教程中,我们介绍了如何:

  • 使用条件逻辑定义表单配置。
  • 构建自定义 Svelte 组件来处理用户交互。
  • 在您的应用中渲染表单并使用动画和自定义 CSS 对其进行样式设置。

请随意尝试其他字段和步骤以适合您的用例。享受构建精彩表单的乐趣! ?


  • ?更多示例
  • ✅ 完整的文档
  • ?加入我们的不和谐
  • ?在 GitHub 上为项目加注星标
  • ❤️赞助英仙座

以上是使用 Svelte 和 Perseid 构建用户反馈表的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn