vue2中:JSON配置动态表单

749 阅读4分钟

根据配置的item列表渲染form表单

2024-12-27 16.11.02.gif

子组件

动态配置Form表单组件: 子组件接收的props.config中元素配置讲解:

[
    {
            label: "姓名",
            prop: "name",
            type: "el-input",
            hidden: false, // 元素是否需要隐藏
            contentSlotName: 'myContent', // 自定义的插槽
            slotName: 'mySlot', // 在el-form-item中的插槽
            attrs: { // 表单元素自身的属性
              disabled: false,
              placeholder: "请输入姓名",
            },
            rules: { required: true, message: "请输入姓名", trigger: "blur" }, // 表单自己的校验规则
            listeners: { // 表单元素自身的事件
              input: (value) => {
                this.formData.name = value;
              },
            },
            elSlots: { // 表单元素如el-input中的prepend或者append,可以输入组件名称或者字符串、数字
              // 1. 组件对象形式
              prepend: {
                autocompleteSlotName: 'myPrepend'
              },
              // 2.字符串形式的append
              append: ".com",
            },
          }
]

dynamic-form.vue:

<template>
  <el-form
    :model="formData"
    ref="dynamicForm"
    v-if="formItemList.length > 0"
    :label-width="labelWidth"
  >
    <div class="dynamic-form-box">
      <template v-for="(item, idx) in formItemList" :key="idx">
        <template v-if="item.contentSlotName">
          <slot
            :item="item"
            :index="idx"
            :key="item.prop + idx"
            :name="item.contentSlotName"
          ></slot>
        </template>
        <template v-else>
          <el-form-item
            v-if="!item.hidden"
            :label="item.label"
            :prop="item.prop"
            :key="item.prop + idx"
            :class="item.className"
            :style="getStyle(item.style, item.width)"
            :rules="item.rules || { required: false }"
          >
            <template v-if="item.slotName">
              <slot
                :item="item"
                :index="idx"
                :name="item.slotName"
                :key="item.prop + idx"
              ></slot>
            </template>

            <component
              v-else
              :is="item.type"
              :key="item.prop + idx"
              v-bind="item.attrs || {}"
              v-on="item.listeners || {}"
              v-model="formData[item.prop]"
            >
              <!-- 处理组件内部插槽 -->
              <template v-for="(slot, slotName) in item.elSlots || {}"
                v-slot:[slotName]
                :key="slotName"
              >
                <div
                  v-if="typeof slot === 'string' || typeof slot === 'number'"
                  v-html="slot"
                ></div>
                <template v-else-if="slot.autocompleteSlotName">
                  <slot :name="slot.autocompleteSlotName" :item="item"></slot>
                </template>
              </template>

              <!-- select选项 -->
              <template v-if="item.type === 'el-select'">
                <el-option
                  v-for="option in item.attrs.options"
                  :key="option.value"
                  :label="option.label"
                  :value="option.value"
                ></el-option>
              </template>

              <!-- checkbox-group选项 -->
              <template v-if="item.type === 'el-checkbox-group'">
                <el-checkbox
                  v-for="option in item.attrs.options"
                  :key="option.value"
                  :value="option.value"
                  >{{ option.label }}</el-checkbox
                >
              </template>

              <!-- radio-group选项 -->
              <template v-if="item.type === 'el-radio-group'">
                <el-radio
                  v-for="option in item.attrs.options"
                  :key="option.value"
                  :label="option.value"
                  :value="option.value"
                  >{{ option.label }}</el-radio
                >
              </template>
            </component>
          </el-form-item>
        </template>
      </template>
    </div>
  </el-form>
</template>

<script>
export default {
  name: "FormTemplate",
  components: {},
  props: {
    labelWidth: {
      type: String,
      default: "100px",
    },
    config: {
      type: Array,
      default: () => [],
    },
    modelValue: {
      type: Object,
      default: null,
    },
  },
  data() {
    return {};
  },
  computed: {
    formData() {
      return this.modelValue || {};
    },
    // 添加计算属性
    formItemList() {
      return (
        this.config || []
      );
    },
  },
  watch: {
    formData: {
      deep: true,
      handler(newVal) {
        this.$emit("update:modelValue", newVal);
      },
    },
  },
  methods: {
    getStyle(style = {}, width = "100%") {
      return {
        ...style,
        width: style.width || width,
      };
    },
    validateForm() {
      return new Promise((resolve, reject) => {
        this.$refs.dynamicForm.validate((valid) => {
          resolve(valid);
        });
      });
    },
  },
};
</script>

<style scoped lang="scss">
.dynamic-form-box {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: wrap;
}
</style>

在父组件中件使用

模板

<template>
  <div class="dynamic-form-container">
    <dynamic-form :config="config" v-model="formData" labelWidth="200" ref="dynamicFormRef">
      <template #title1="{ item }">
        <p class="title">{{ item.title }}</p>
      </template>
      <template #title2="{ item }">
        <p class="title">{{ item.title }}</p>
      </template>
      <template #title3="{ item }">
        <p class="title">{{ item.title }}</p>
      </template>
      <template #lineFeed>
        <p class="line-feed"></p>
      </template>
      <template #pwdAppend="{ item }">
        <span class="my-append">{{ item.appendText }}</span>
      </template>
      <template #subAccountAppend>
        <span class="my-append">账号</span>
      </template>
    </dynamic-form>

    <el-button @click="submitForm">提交</el-button>
  </div>
</template>

JS

<script>
import dynamicForm from "./components/dynamic-form.vue";
export default {
  name: "FormTemplate",
  components: {
    dynamicForm,
  },
  data() {
    return {
      formData: {
        companyId: "bb123d50f96d45558270260a22612",
        companyName: "test公司名称",
        englishName: "en",
        mainAccount: "admin@qcc.com",
        password: "",
        phone: "",
        scene: "",
        passwordPolicy: "0",
        passwordResetDays: 90,
        userInactiveTimeout: 15,
        mfa: "1",
        isInternal: "1",
        isApiService: "0",
        subAccountCount: "10",
        startDate: "",
        role: ["1"],
        startDate: ""
      },
    };
  },
  computed: {
    config() {
      const list = [
        {
          contentSlotName: "title1",
          title: "公司基本信息",
        },
        {
          label: "公司ID",
          prop: "companyId",
          type: "el-input",
          hidden: false,
          attrs: {
            disabled: true,
            placeholder: "请输入公司ID",
          },
        },
        {
          label: "公司名称",
          prop: "companyName",
          type: "el-input",
          hidden: false,
          attrs: {
            disabled: false,
            placeholder: "请输入公司名称",
          },
        },
        {
          label: "英文名称",
          prop: "englishName",
          type: "el-input",
          hidden: false,
          attrs: {
            disabled: false,
            placeholder: "请输入英文名称",
          },
        },
        {
          label: "主账号登录名",
          prop: "mainAccount",
          type: "el-input",
          hidden: false,
          attrs: {
            disabled: false,
            placeholder: "请输入主账号登录名",
          },
        },
        {
          label: "密码",
          prop: "password",
          type: "el-input",
          hidden: false,
          appendText: "生成密码",
          attrs: {
            placeholder: "请输入密码",
            showPassword: true,
          },
          elSlots: {
            append: {
              autocompleteSlotName: "pwdAppend",
            },
          },
        },
        {
          label: "联系电话",
          prop: "phone",
          type: "el-input",
          hidden: false,
          attrs: {
            disabled: false,
            placeholder: "请输入联系电话",
          },
        },
        {
          label: "应用场景",
          prop: "scene",
          type: "el-input",
          hidden: false,
          attrs: {
            rows: 4,
            disabled: false,
            type: "textarea",
            placeholder: "请输入应用场景",
          },
        },

        {
          contentSlotName: "title2",
          title: "账号设置",
        },
        {
          label: "密码策略",
          prop: "passwordPolicy",
          type: "el-radio-group",
          hidden: false,
          attrs: {
            disabled: false,
            options: [
              { label: "增强密码", value: "1" },
              { label: "普通密码", value: "0" },
            ],
            "popper-options": {
              modifiers: [
                { name: "computeStyles", options: { adaptive: false } },
              ],
            },
          },
        },
        {
          label: "账号密码强制重置天数",
          prop: "passwordResetDays",
          type: "el-input",
          hidden: false,
          width: "390px",
          attrs: {
            disabled: true,
            placeholder: "请输入账号密码强制重置天数",
          },
          elSlots: {
            append: "天",
          },
        },
        {
          contentSlotName: "lineFeed",
        },
        {
          label: "用户未操作时超时时间",
          prop: "userInactiveTimeout",
          type: "el-input",
          hidden: false,
          width: "390px",
          attrs: {
            disabled: true,
            placeholder: "请输入用户未操作时超时时间",
          },
          elSlots: {
            append: "分钟",
          },
        },
        {
          label: "二次认证",
          prop: "mfa",
          type: "el-radio-group",
          hidden: false,
          attrs: {
            disabled: false,
            options: [
              { label: "Google MFA", value: "1" },
              { label: "无", value: "0" },
            ],
          },
        },
        {
          label: "是否是内部账号",
          prop: "isInternal",
          type: "el-radio-group",
          hidden: false,
          attrs: {
            disabled: false,
            options: [
              { label: "是", value: "1" },
              { label: "否", value: "0" },
            ],
          },
        },
        {
          label: "是否开启API服务",
          prop: "isApiService",
          type: "el-radio-group",
          hidden: false,
          attrs: {
            disabled: true,
            options: [
              { label: "是", value: "1" },
              { label: "否", value: "0" },
            ],
          },
        },
        {
          label: "子账号数量",
          prop: "subAccountCount",
          type: "el-input",
          hidden: false,
          attrs: {
            placeholder: "请输入子账号数量",
          },
          rules: {
            required: true,
            trigger: ["blur", "change"],
            validator: (rule, value, callback) => {
              if (!value) {
                callback(new Error("请输入子账号数量"));
              } else if (isNaN(value)) {
                callback(new Error("请输入数字"));
              } else if (value < 1) {
                callback(new Error("请输入大于0的数字"));
              } else {
                callback();
              }
            },
          },
          elSlots: {
            append: {
              autocompleteSlotName: "subAccountAppend",
            },
          },
        },
        {
          label: "开始日期",
          prop: "startDate",
          type: "el-date-picker",
          hidden: false,
          attrs: {
            type: "date",
            placeholder: "请选择开始日期",
            valueFormat: "yyyy MM dd",
            popper: {
              modifiers: [
                { name: "computeStyles", options: { adaptive: false } },
              ],
            },
            "popper-options": {
              modifiers: [
                { name: "computeStyles", options: { adaptive: false } },
              ],
            },
          },
          listeners: {
            change: (value) => {
              this.formData.startDate = value;
            },
          },
        },

        {
          contentSlotName: "title3",
          title: "权益与单价",
        },
        {
          label: "角色",
          prop: "role",
          type: "el-select",
          hidden: false,
          attrs: {
            placeholder: "请选择角色",
            clearable: true,
            multiple: true,
            options: [
              { label: "内部服务", value: "1" },
              { label: "公共模块", value: "2" },
            ],
            "popper-options": {
              modifiers: [
                { name: "computeStyles", options: { adaptive: false } },
              ],
            },
          },
        },
      ];

      return list;
    },
  },
  watch: {
    formData: {
      deep: true,
      handler(newVal) {
        console.log(1, newVal);
      },
    },
  },
  methods: {
    submitForm() {
      this.$refs.dynamicFormRef.validateForm().then((flag) => {
        if (flag) {
          console.log("表单验证通过");
        } else {
          console.log("表单验证不通过");
        }
      });
    },
  },
};
</script>