vue3通过json动态生成简单的表单

1,147 阅读3分钟

前言

表单十分复杂而且繁多的情况下前端写起来能累趴,通过json生成表单可以帮助前端根据后端提供的数据结构灵活地创建用户界面。这种方法特别适合于需要根据配置动态展示不同表单字段的场景,比如构建一个表单构建器、配置页面或者任何需要高度定制化的表单。

注意:这只是个简单的版本,主要还是给大家提供一个思路。

确定数据结构

大概的数据结构是这样的,具体什么结构还得和后端商量一下。

const uiSchame = ref([
  {
    name: "check_type",
    type: "select_item",
    title: "审核类型",
    prompt_msg: "请填写审核类型",
    selectObj: [
      {
        id: 1,
        label: "预审核",
        value: "1",
      },
      {
        id: 2,
        label: "患者审核",
        value: "2",
      },
    ]
  },
  {
    name: "bank_branch_info",
    type: "input_item",
    title: "支行信息",
    prompt_msg: "请填写支行信息",
    selectObj: null,
  }
]);

以上结构显而易见了,一个对象代表一个组件
简单介绍一下关键的属性:

  • name: 组件的名字,后面 formData中会用到
  • type: 代表使用的是什么类型的组件(input、 select、 radio.....)
  • selectObj: 如果是select就有数据,默认是空

封装组件

根据自己的需求进行封装,这里就演示两个,在components中创建form-items文件夹。这里用到了elementui组件库,需要先下载一下。

input组件

<script setup>
import { ref } from "vue";
const props = defineProps({
  items: Object,
});

const value = ref("");

</script>

<template>
  <div>
    <el-input v-model="value" />
  </div>
</template>

select组件

<script setup>
import { ref, defineOptions } from "vue";
const porps = defineProps({
  items: Object,
});

const value = ref("");
</script>

<template>
  <div>
    <el-select
      v-model="value"
      placeholder="Select"
      size="large"
      style="width: 240px"
    >
      <el-option
        v-for="item in items.selectObj"
        :key="item.value"
        :label="item.label"
        :value="item.value"
      />
    </el-select>
  </div>
</template>

创建index.js导出组件

form-items/index.js中添加如下代码:

import select_item from "./select_item.vue";
import input_item from "./input_item.vue";

export default {
  select_item,
  input_item,
};

dynamic-form组件

这个是生成form表单的组件,使用的是动态组件,详情可以看vue的文档动态组件。 注意看以下代码中我的component组件中的is属性,我是通过type动态选取组件的,不难看出这个type就是最初定义的uiSchame对象里的。

<script setup>
import components from "./form-items/index"; // 将所有组件引入

defineOptions({ name: "DynamicForm" });

const props = defineProps({
  uiSchame: {
    type: Array,
    default: () => [],
  }
});

defineExpose({ formData: props.formData });
</script>

<template>
  <div>
    <component
      v-for="item in uiSchame"
      :is="components[item.type]"
      :items="item"
    ></component>
  </div>
</template>

使用dynamic-form组件

在App.vue中写如下代码

<script setup>
import DynamicForm from "./components/dynamic-form.vue";

import { ref } from "vue";

const uiSchame = ref([
  {
    name: "check_type",
    type: "select_item",
    title: "审核类型",
    prompt_msg: "请填写审核类型",
    selectObj: [
      {
        id: 1,
        label: "预审核",
        value: "1",
      },
      {
        id: 2,
        label: "患者审核",
        value: "2",
      },
    ]
  },
  {
    name: "bank_branch_info",
    type: "input_item",
    title: "支行信息",
    prompt_msg: "请填写支行信息",
    selectObj: null,
  }
]);
</script>

<template>
  <div>
    <DynamicForm :ui-schame="uiSchame"/>
  </div>
</template>

这样就能动态渲染两个组件:

image.png

数据收集

动态渲染组件实现了,但是如何获取数据呢。

首先安装一个依赖npm i mitt,官方文档mitt。这就是事件总线,可以夸组触发事件、传递数据,看我演示就行了。

创建utils/mitter.js, 添加如下代码:

import mitt from "mitt";

const emitter = mitt();

export default emitter;

在App.vue中修改如下代码

<script setup>
import DynamicForm from "./components/dynamic-form.vue";

import { ref } from "vue";

const uiSchame = ref([
  {
    name: "check_type",
    type: "select_item",
    title: "审核类型",
    prompt_msg: "请填写审核类型",
    selectObj: [
      {
        id: 1,
        label: "预审核",
        value: "1",
      },
      {
        id: 2,
        label: "患者审核",
        value: "2",
      },
    ]
  },
  {
    name: "bank_branch_info",
    type: "input_item",
    title: "支行信息",
    prompt_msg: "请填写支行信息",
    selectObj: null,
  }
]);

// 表单数据
const formData = ref({
  check_type: "",
  bank_branch_info: "",
  project_content: "",
});

// 当dynamic-form组件中修改数据之后触发,给 formData赋值
const formDataChange = (val) => {
  formData.value = val;
};

// 点击提交按钮触发
const formSubmit = () => {
  console.log(formData.value);
};
</script>

<template>
  <div>
    <DynamicForm
      @formDataChange="formDataChange"
      :formData="formData"
      :ui-schame="uiSchame"
    />
    <el-button type="primary" @click="formSubmit">提交</el-button>
  </div>
</template>

在dynamic-form组件中代码修改如下

<script setup>
import components from "./form-items/index";
import mitter from "./utils/mitter";
import { watch } from "vue";

defineOptions({ name: "DynamicForm" });

const emit = defineEmits(["formDataChange"]);

const props = defineProps({
  uiSchame: {
    type: Array,
    default: () => [],
  },
  formData: {
    type: Object,
    default: () => {},
  },
});

// 监听chageVal
mitter.on("changeVal", (data) => {
  const { name, value } = data;
  if (name) {
    // 这里的name就是之前在uiSchame中定义的属性
    // 可以通过这个name获取相关属性进行赋值
    props.formData[name] = value;
  }
});

// 监听formData的变化,触发父组件中的formDataChange改变数据
watch(
  () => props.formData,
  (newVal) => {
    emit("formDataChange", newVal);
  },
  { deep: true }
);

defineExpose({ formData: props.formData });
</script>

<template>
  <div>
    <component
      v-for="item in uiSchame"
      :is="components[item.type]"
      :items="item"
    ></component>
  </div>
</template>

在 input组件中使用。代码修改如下

<script setup>
import mitter from "../utils/mitter";
import { ref } from "vue";
const props = defineProps({
  items: Object,
});

const value = ref("");

// 当输入的时候触发chageVal事件给formData中的相关数据赋值,这里传递了name和value
const input = () => {
  mitter.emit("changeVal", { name: props.items.name, value: value.value });
};
</script>

<template>
  <div>
    <el-input v-model="value" @input="input" />
  </div>
</template>

select组件代码修改如下

<script setup>
import { ref } from "vue";
import emitter from "../utils/mitter";
const porps = defineProps({
  items: Object,
});

const value = ref("");

// 选择某一项之后触发changeVale事件
const onChangeFirstValue = (val) => {
  emitter.emit("changeVal", { name: porps.items.name, value: value.value });
};
</script>

<template>
  <div>
    <el-select
      v-model="value"
      placeholder="Select"
      size="large"
      style="width: 240px"
      @change="onChangeFirstValue(value)"
    >
      <el-option
        v-for="item in items.selectObj"
        :key="item.value"
        :label="item.label"
        :value="item.value"
      />
    </el-select>
  </div>
</template>

至此就完成了数据的收集。最后我在捋一下思路:

子组件通过mitt触发dynamic-form中的chageVal事件改变props.formData的数据,当数据改变的时候通过watch触发formDataChange事件在App.vue中改变formData数据从而得到最终数据。 以上只是一个简单的版本,主要还是给大家提供一个思路完整代码