Skip to content

表单解决大量表单的通用处理组件

上次更新 2024年11月5日星期二 15:35:8 字数 0 字 时长 0 分钟

为了表单解决大量表单的通用处理组件

基础用法

基本信息
姓名
年龄
内容分类
颜色
输入框
滑块
选项
请输入内容
复选框
单选

编辑表单(重置表单)

基本信息
姓名
年龄
内容分类
颜色
输入框
滑块
选项
复选框
单选

新增表单(重置表单)

基本信息
姓名
年龄
内容分类
颜色
输入框
滑块
选项
请输入内容
复选框
单选
HcFormDemo
HcFormDemo.vue
vue
<script setup>
import { ElButton } from "element-plus";
import { computed, ref } from "vue";
import useForm from "./index.ts";
import HcForm from "./index.vue";

const { options } = useForm()

const formProps = ref({
  labelWidth: 100,
  labelPosition: "left",
});

const wrapperRow = ref({
  gutter: 20,
});
const wrapperCol = ref({
  span: 12,
});
const hcFormRef = ref(null);

const modelValue = computed(() => hcFormRef.value?.modelValue);

const { options: options2, initForm: initForm2 } = useForm()
const { resetForm: resetForm2 } = initForm2('edit', { "name": "张三", "age": 24, "color": "#361A9E", "input004": "输入框1", "count": 29, "input006": "option2", "checkbox": ["option2"], "radio": "option2" })

const formProps2 = ref({
  labelWidth: 100,
  labelPosition: "left",
});

const wrapperRow2 = ref({
  gutter: 20,
});
const wrapperCol2 = ref({
  span: 12,
});
const hcFormRef2 = ref(null);

const modelValue2 = computed(() => hcFormRef2.value?.modelValue);




const { options: options3, initForm: initForm3 } = useForm()
const { resetForm: resetForm3 } = initForm3('create', { "name": "张三", "age": 24, "color": "#361A9E", "input004": "输入框1", "count": 29, "input006": "option2", "checkbox": ["option2"], "radio": "option2" })

const formProps3 = ref({
  labelWidth: 100,
  labelPosition: "left",
});

const wrapperRow3 = ref({
  gutter: 20,
});
const wrapperCol3 = ref({
  span: 12,
});
const hcFormRef3 = ref(null);

const modelValue3 = computed(() => hcFormRef3.value?.modelValue);


// const separateComp = shallowRef(SeparateComp)
</script>

<template>
  <div class="w-full">
    <div class="demo-item">
      <h4 class="demo-title">基础用法</h4>
      <HcForm :options="options" ref="hcFormRef" :form-props="formProps" :wrapperRow="wrapperRow"
        :wrapper-col="wrapperCol" />
      {{ modelValue }}
    </div>


    <div class="demo-item">
      <h4 class="demo-title">编辑表单(重置表单)</h4>
      <HcForm :options="options2" ref="hcFormRef2" :form-props="formProps2" :wrapperRow="wrapperRow2"
        :wrapper-col="wrapperCol2" />
      {{ modelValue2 }}
      <div class="flex justify-center my-4">
        <el-button type="primary" @click="resetForm2">重置</el-button>
      </div>
    </div>


    <div class="demo-item">
      <h4 class="demo-title">新增表单(重置表单)</h4>
      <HcForm :options="options3" ref="hcFormRef3" :form-props="formProps3" :wrapperRow="wrapperRow3"
        :wrapper-col="wrapperCol3" />
      {{ modelValue3 }}
      <div class="flex justify-center my-4">
        <el-button type="primary" @click="resetForm3">重置</el-button>
      </div>
    </div>
  </div>
</template>
<style lang="scss" scoped>
.demo-item {
  @apply p-4 border rounded-md;

  .demo-title {
    @apply p-2 w-full mb-2;
  }

  &:not(:last-child) {
    @apply mb-4;
  }
}
</style>
HCForm
index.vue
vue
<script setup lang="ts">
import {
  ElCheckbox,
  ElCheckboxGroup,
  ElCol,
  ElColorPicker,
  ElForm,
  ElFormItem,
  ElInput,
  ElInputNumber,
  ElOption,
  ElRadio,
  ElRadioGroup,
  ElRow,
  ElSelect,
  ElSlider,
  ElSwitch,
} from "element-plus";
import {
  defineExpose,
  defineProps,
  PropType,
  reactive,
  ref,
  watchEffect,
} from "vue";

const components = {
  number: ElInputNumber,
  switch: ElSwitch,
  radio: ElRadioGroup,
  checkbox: ElCheckboxGroup,
  slider: ElSlider,
  color: ElColorPicker,
  select: ElSelect,
  input: ElInput,
};

type optionsType = {
  component: keyof typeof components;
  props: any;
  label: any;
  field: string;
  value?: string;
  rules?: [];
  // 分栏标题
  isSeparate?: boolean;
  icon?: any;
};
const getComponent = (component: keyof typeof components) => {
  return components[component];
};
const props = defineProps({
  options: { type: Array as PropType<optionsType[]> },
  formProps: {
    type: Object,
    default: () => ({}),
  },
  wrapperRow: { type: Object, default: () => ({}) },
  wrapperCol: { type: Object, default: () => ({}) },
  separateComp: {}, // 分栏组件
});

const formEl = ref();

const fromItemEls = ref<any[]>([]);

const modelValue = reactive<any>({});

const renderOptions = ref<optionsType[]>([]);

const rules = reactive<any>({});

watchEffect(() => {
  renderOptions.value = props.options;
  renderOptions.value.forEach((item: optionsType) => {
    modelValue[item.field] = item.value;
    if (item.rules) {
      rules[item.field] = item.rules;
    }
  });
});
defineExpose({
  modelValue,
  formEl: new Proxy(
    {},
    {
      get(target, key) {
        return formEl.value?.[key];
      },
    }
  ),
  fromItemEls,
});
</script>

<template>
  <el-form v-bind="{ 'require-asterisk-position': 'right', ...$props.formProps }" ref="formEl" :rules="rules">
    <el-row v-if="$props.wrapperRow" v-bind="$props.wrapperRow">
      <template v-for="item in renderOptions">
        <el-col v-if="item.isSeparate" v-bind="{ span: 24 }">
          <template v-if="!$props.separateComp">
            <div class="flex text-md bg-teal-400  px-3 py-2 rounded-md mb-2 text-white">
              <div class=""></div>
              <div class="font-bold">{{ item.label }}</div>
            </div>
          </template>
          <template v-else>
            <component :is="$props.separateComp" :label="item?.label" :icon="item?.icon"></component>
          </template>
        </el-col>
        <el-col v-else-if="$props.wrapperCol" v-bind="$props.wrapperCol">
          <el-form-item v-bind="item.props" :label="item?.label" :prop="item?.field" ref="fromItemEls">
            <component :is="getComponent(item.component)" v-bind="item?.props || {}" v-model="modelValue[item.field]">
              <template v-if="item.component === 'select'">
                <el-option v-for="(option, index) in item.props.options" :key="index" :label="option.label"
                  :value="option.value"></el-option>
              </template>
              <template v-else-if="item.component === 'radio'">
                <el-radio v-for="(option, index) in item.props.options" :key="index" :label="option.label"
                  :value="option.value"></el-radio>
              </template>
              <template v-else-if="item.component === 'checkbox'">
                <el-checkbox v-for="(option, index) in item.props.options" :key="index" :label="option.label"
                  :value="option.value"></el-checkbox>
              </template>
            </component>
          </el-form-item>
        </el-col>
      </template>
    </el-row>
  </el-form>
</template>

<style scoped lang="scss" src="./index.css"></style>
useForm
index.ts
ts
import { ref } from "vue";

const useForm = () => {
  const options = ref([
    {
      isSeparate: true,
      label: "基本信息",
      icon: "icon",
    },
    {
      label: "姓名",
      component: "input",
      field: "name",
      value: "张三",
      props: {
        type: "text",
        placeholder: "请输入内容",
      },
      rules: [
        {
          required: true,
          message: "Please input Activity name",
          trigger: "blur",
        },
        { min: 3, max: 5, message: "Length should be 3 to 5", trigger: "blur" },
      ],
    },
    {
      label: "年龄",
      component: "number",
      field: "age",
      props: {
        type: "text",
        placeholder: "请输入内容",
        controls: false,
      },
      rules: [
        {
          required: true,
          message: "Please input Activity name",
          trigger: "blur",
        },
        { min: 3, max: 6, message: "Length should be 3 to 6", trigger: "blur" },
      ],
    },
    {
      isSeparate: true,
      label: "内容分类",
      icon: "icon",
    },
    {
      label: "颜色",
      component: "color",
      field: "color",
      props: {
        type: "text",
        placeholder: "请输入内容",
      },
    },
    {
      label: "输入框",
      component: "input",
      field: "input004",
      props: {
        type: "text",
        placeholder: "请输入内容",
      },
    },
    {
      label: "滑块",
      component: "slider",
      field: "count",
      props: {
        type: "text",
        placeholder: "请输入内容",
      },
    },
    {
      label: "选项",
      component: "select",
      field: "input006",
      props: {
        type: "text",
        placeholder: "请输入内容",
        options: [
          { value: "option1", label: "Option 1" },
          { value: "option2", label: "Option 2" },
          { value: "option3", label: "Option 3" },
        ],
      },
    },
    {
      label: "复选框",
      component: "checkbox",
      field: "checkbox",
      props: {
        type: "text",
        placeholder: "请输入内容",
        options: [
          { value: "option1", label: "Option 1" },
          { value: "option2", label: "Option 2" },
          { value: "option3", label: "Option 3" },
        ],
      },
    },
    {
      label: "单选",
      component: "radio",
      field: "radio",
      props: {
        type: "text",
        placeholder: "请输入内容",
        options: [
          { value: "option1", label: "Option 1" },
          { value: "option2", label: "Option 2" },
          { value: "option3", label: "Option 3" },
        ],
      },
    },
  ]);

  let optionsTemp = JSON.parse(JSON.stringify(options.value));

  /**
   * @description 将表单数据与options融合
   * @author yhx
   * @param data  表单数据
   * @return  {Array} 融合后的数据
   */
  const mergeOptions = (data: any) => {
    // 深拷贝数据
    const mergeData = JSON.parse(JSON.stringify(options.value));

    // 将 data数据与options融合
    mergeData.forEach((option: any) => {
      if (option.field in data) {
        option.value = data[option.field];
      }
    });
    options.value = mergeData;
    return mergeData;
  };

  /**
   * @description 初始化表单状态
   * @author yhx
   * @param  {Object} config 表单配置项
   *
   */
  const initForm = (state: string, data: any) => {
    let resetForm = null as any;
    switch (state) {
      case "details": // 详情状态
        mergeOptions(data);
        break;
      case "edit": // 编辑状态
        mergeOptions(data);
        resetForm = () => {
          options.value = mergeOptions(data);
        }; // 重置表单编辑状态
        break;
      case "create": // 创建状态
        resetForm = () => (options.value = optionsTemp); // 重置表单编辑状态
        break;
    }
    return { resetForm };
  };

  return { options, mergeOptions, initForm };
};

export default useForm;

上次更新: