Vue3.0 根据JSON对象生成指定form表单

在平常开发中,form表单是写的最多且最繁琐的一个功能。为了避免代码冗余,我把form表单抽出来当做一个组件并且根据数据来动态生成基本表单

现在只写了input和select 后续会加上更多,且优化双向绑定的代码

用到的技术

1.Vue3基础知识
2.Element-plus组件

定义formTemplate

判断是否需要布局(el-col)
判断是否需要form包裹(el-form-item)
根据传递的参数来生成指定组件(components):比如输入框 下拉框等

注意:使用的时候需要全局注册一下指定组件 具体看 倒数第二步 全局注册组件

formTemplate.vue

<template>
  <el-col v-if="isCol" :span="colSpan">
    <div v-if="isForm">
      <el-form-item :prop="prop" :label="label">
        <component
          :is="isCom"
          :config="config"
          v-model="templateValue"
          @change="change"
        />
      </el-form-item>
    </div>
    <div v-else>
      <component
        :is="isCom"
        :config="config"
        v-model="templateValue"
        @change="change"
      />
    </div>
  </el-col>
  <div v-else>
    <div v-if="isForm">
      <el-form-item :prop="prop" :label="label">
        <component
          :is="isCom"
          :config="config"
          v-model="templateValue"
          @change="change"
        />
      </el-form-item>
    </div>
    <div v-else>
      <component
        :is="isCom"
        :config="config"
        v-model="templateValue"
        @change="change"
      />
    </div>
  </div>
</template>

<script setup>
import { watch, ref } from "vue";

/**
 * @author BlackKey
 * @params modelValue 双向绑定值
 * @params colSpan 布局占位
 * @params isCol 是否启用布局
 * @params isForm 是否用form包裹
 * @params label 标题
 * @params prop model的键名
 * @params isCom 需要的生成的组件
 * @params config 组件对应的配置文件
 * @desc  form表单默认格式
 */

const { colSpan, isCol, isForm, label, prop, isCom, modelValue, config } =
  defineProps({
    colSpan: {
      type: Number,
      default: 6,
    },
    isCol: {
      type: Boolean,
      default: true,
    },
    isForm: {
      type: Boolean,
      default: true,
    },
    label: {
      type: String,
      default: "",
    },
    prop: {
      type: String,
      default: "",
    },
    isCom: {
      type: String,
      default: "wx-input",
    },
    modelValue: {
      type: [String, Number, Object, Array],
      default: "",
    },
    config: {
      type: Object,
      default: () => ({}),
    },
  });
const templateValue = ref(modelValue);
const emit = defineEmits(["change"]);
const change = (data) => {
  emit("change", data);
};
</script>

<style lang="less" scoped></style>

默认输入框组件

wxInput.vue

<template>
  <el-input
    v-model="inputValue"
    :placeholder="placeholder"
    :clearable="clearable"
    :disabled="disabled"
    :type="type"
    :maxlength="maxlength"
    :autosize="autosize"
  />
</template>

<script setup>
import { watch, ref } from "vue";
/**
 * @author BlackKey
 * @params modelValue 双向绑定值
 * @params placeholder 输入框占位符文本
 * @params type 输入框类型
 * @params maxlength 最大长度
 * @params disabled 是否禁用
 * @params size 输入框尺寸
 * @params autosize textarea 高度是否自适应,仅 type 为 'textarea' 时生效。
 * @params clearable 是否展示清空按钮
 * @desc  输入框
 */

const { config, modelValue } = defineProps({
  config: {
    type: Object,
    default: () => ({}),
  },
  modelValue: {
    type: [String, Number, Object, Array],
    default: "",
  },
});
const {
  placeholder = "",
  type = "text",
  maxlength = "",
  disabled = false,
  size = "",
  autosize = false,
  clearable = true,
} = config;

const emit = defineEmits(["change"]);
const inputValue = ref(modelValue);
watch(
  () => inputValue.value,
  (data) => {
    change(data);
  }
);
const change = (data) => {
  emit("change", data);
};
</script>

默认下拉框组件

为了防止枚举写一遍 options又写一遍的情况 我把枚举值引入进来了 (baseSelectSource)
如果枚举写了,就直接传dataType即可

meta.js

const sex = [
  {
    label: "男",
    value: "man",
  },
  {
    label: "女",
    value: "woman",
  },
];

export const baseSelectSource = {
  sex,
};

wxSelect.vue

<template>
  <el-select
    v-model="selectValue"
    :clearable="clearable"
    :placeholder="placeholder"
    :disabled="disabled"
    :size="size"
  >
    <el-option
      v-for="item of (options.length && options) || source || []"
      :label="item[label]"
      :value="item[value]"
    ></el-option>
  </el-select>
</template>

<script setup>
import { watch, ref } from "vue";
import { baseSelectSource } from "@/meta";
/**
 * @author BlackKey
 * @params modelValue 双向绑定值
 * @params placeholder 输入框占位符文本
 * @params disabled 是否禁用
 * @params size 输入框尺寸
 * @params clearable 是否展示清空按钮
 * @params options 下拉框的值
 * @params dataType 预先设定好的枚举
 * @params labelKey 下拉框labelKey
 * @params valueKey 下拉框valueKey
 * @desc  下拉框
 */

const { config, modelValue } = defineProps({
  config: {
    type: Object,
    default: () => ({}),
  },
  modelValue: {
    type: [String, Number, Object, Array],
    default: "",
  },
});
const {
  placeholder = "",
  disabled = false,
  size = "",
  clearable = true,
  options = [],
  dataType = "",
  labelKey = "label",
  valueKet = "value",
} = config;

// 如果dateType有值 代表下拉框取枚举值 从meta里面获取数组用来展示下拉框
const source = ref([]);
if (dataType) {
  source.value = baseSelectSource[dataType];
}

const emit = defineEmits(["change"]);
// 接收labelKey和valueKey且绑定
const label = ref(labelKey);
const value = ref(valueKet);
const selectValue = ref(modelValue);
watch(
  () => selectValue.value,
  (data) => {
    change(data);
  }
);
const change = (data) => {
  emit("change", data);
};
</script>

全局注册组件

使用该组件的时候如果不全局注册需要的组件 这样会导致生成失败
registerComponent.js

import FormTemplate from "@/components/FormTemplate/index.vue";
import wxInput from "@/components/FormTemplate/components/wxInput.vue";
import wxSelect from "@/components/FormTemplate/components/wxSelect.vue";

export default function install(app) {
  app.component("wx-form", FormTemplate);
  app.component("wx-input", wxInput);
  app.component("wx-select", wxSelect);
}

注意:记得在main里面引入registerComponent.js文件 否则注册失败

main.js

import { createApp } from "vue";
import App from "./App.vue";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import "@/assets/css/index.less";
import registerComponent from "./registerComponent";

const app = createApp(App);

app.use(registerComponent);
app.use(ElementPlus);
app.mount("#app");

使用方法

index.vue

注意:使用时记得用el-form和el-row包裹一下 因为我设置的默认使用布局和form包裹住

<template>
  <el-form :model="form" ref="formRef" label-width="80px">
    <el-row>
      <wx-form
        v-for="item in formTemplate"
        :label="item.label"
        :config="item.config"
        :isCom="item.isCom"
        @change="item.modelValue = $event"
        v-model="item.modelValue"
        ></wx-form>
      <el-col :span="24">
        <div class="btn">
          <el-button type="primary" @click="search">查询</el-button>
        </div>
      </el-col>
    </el-row>
  </el-form>
</template>

<script setup>
  import { reactive, toRefs, ref } from "vue";
  const userData = reactive({
    form: { name: "", account: "", tel: "", sex: "" },
  });
  const { form } = { ...toRefs(userData) };

  const formTemplate = ref([
    {
      label: "昵称",
      modelValue: form.value.name,
      prop: "name",
      config: {
        placeholder: "请输入昵称",
      },
    },
    {
      label: "账号",
      modelValue: form.value.account,
      prop: "account",
      config: {
        placeholder: "请输入账号",
      },
    },
    {
      label: "手机",
      modelValue: form.value.tel,
      prop: "tel",
      config: {
        placeholder: "请输入手机",
      },
    },
    {
      label: "性别",
      modelValue: form.value.sex,
      isCom: "wx-select",
      prop: "sex",
      config: {
        placeholder: "请选择性别",
        dataType: "sex",
      },
    },
  ]);

  // 查询
  const search = () => {
    formTemplate.value.forEach((val) => {
      form.value[val.prop] = val.modelValue;
    });
    console.log(form.value);
  };
</script>

<style lang="less" scoped>
  .btn {
    text-align: right;
  }
</style>

效果图

效果图
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容

  • 注意:升级前,node版本需要在8.9的版本以上! 本文目录: 为什么要升级框架? 主要模块升级对应版本 vue2...
    绘梦_阅读 9,436评论 0 2
  • 一、了解Vue.js 1.1.1 Vue.js是什么? 简单小巧、渐进式、功能强大的技术栈 1.1.2 为什么学习...
    蔡华鹏阅读 3,309评论 0 3
  • Vue.js(读音/vjuː/, 类似于 view)是一个构建数据驱动的web 界面的渐进式框架。Vue.js的目...
    xingyunfuhao阅读 556评论 0 0
  • Vue学习框架 1 Vue 2 Vue框架 3 Vue问题 4 Vue生命周期 参考:https://cn.vue...
    司空洛一阅读 392评论 0 0
  • 正在更新中... 声明:所有文章都是转载整理的,只是为了自己学习,方便自己观看,如有侵权,请立即联系我,谢谢~ V...
    是河兔兔啊阅读 4,484评论 0 1