vue2.7 + element ui 动态表单

本文介绍了一个Vue项目中动态表单的实现,包括使用v-for循环创建表单,动态绑定model以响应数据变化,处理表单验证,以及如何通过Object.assign进行响应式赋值。同时,文章提到了在处理级联选择(如省市区下拉框)时,确保初始值的设定以确保正确显示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、代码

<template>
  <!--其他表单信息-->
  <div v-loading="loading" class="otherFrom">
    <div v-for="(item, index) in formList" :key="index">
      <div class="title">
        {{ item.name }}
        <i v-if="item.ifExpand === 1" class="el-icon-circle-plus-outline addBtn" @click="addRow(item)"></i>
      </div>
      <el-form
        ref="infoFormRefList"
        :model="item.columnObj"
        label-width="150px"
        class="form"
        style="width: 920px; margin: 24px auto 0"
      >
        <el-row v-for="(rowNumItem, rowNumIndex) in item.rowNum" :key="rowNumIndex">
          <el-col v-for="(it, idx) in item.attributetemplateList" :key="idx" :span="12">
            <el-form-item
              v-if="it.dictionarytypeId === 0"
              :label="it.name"
              :prop="`${it.code}${rowNumIndex}`"
              :rules="{
                required: it.notnull,
                message: `${it.name}不能为空`,
                trigger: 'blur',
              }"
            >
              <el-input
                v-if="it.dataTypeName === '字符'"
                v-model.trim="item.columnObj[`${it.code}${rowNumIndex}`]"
                :type="it.type === 1111 ? 'textarea' : 'input'"
                :disabled="it.disabled"
                :placeholder="it.placeholder"
              ></el-input>
              <el-input-number
                v-if="it.dataTypeName === '小数'"
                v-model.trim="item.columnObj[`${it.code}${rowNumIndex}`]"
                :precision="2"
                :step="0.1"
              ></el-input-number>
              <el-input-number
                v-if="it.dataTypeName === '整数' || it.dataTypeName === '长整数'"
                v-model.trim="item.columnObj[`${it.code}${rowNumIndex}`]"
                :step="1"
                step-strictly
              ></el-input-number>
              <el-date-picker
                v-if="it.dataTypeName === '日期'"
                v-model.trim="item.columnObj[`${it.code}${rowNumIndex}`]"
                type="date"
                :placeholder="it.placeholder"
                value-format="yyyy-MM-dd"
              >
              </el-date-picker>
              <el-date-picker
                v-if="it.dataTypeName === '日期时间'"
                v-model.trim="item.columnObj[`${it.code}${rowNumIndex}`]"
                type="datetime"
                :placeholder="it.placeholder"
                value-format="yyyy-MM-dd HH:mm:ss"
              >
              </el-date-picker>
            </el-form-item>
            <el-form-item
              v-else
              :label="it.name"
              :prop="`${it.code}${rowNumIndex}`"
              :rules="{
                required: it.notnull,
                message: `${it.name}不能为空`,
                trigger: 'change',
              }"
            >
              <el-select v-model.trim="item.columnObj[`${it.code}${rowNumIndex}`]" filterable clearable>
                <el-option
                  v-for="(dictItem, dictIndex) in it.dictList"
                  :key="dictIndex"
                  :label="dictItem.name"
                  :value="dictItem.value"
                ></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col v-if="item.rowNum > 1">
            <el-divider></el-divider>
          </el-col>
        </el-row>
      </el-form>
    </div>
    <el-divider></el-divider>
  </div>
</template>
<script lang="ts" setup>
import { useRoute } from 'vue-router/composables';
import { getItemAttribute, findDictTypeListByTypeIds, getFormInfo, createOrUpdateForm } from '@/apis/handle';

import { ref, onMounted } from 'vue';
import { Message } from 'element-ui';

const route = useRoute();
const loading = ref<boolean>(false);
const props = withDefaults(
  defineProps<{
    catalogCode?: string;
    projectNo?: string;
  }>(),
  {
    catalogCode: '',
    projectNo: '',
  },
);
// const dataTypeList = ref<any>([
//   // 表单项数据类型
//   { name: '字符', id: 'varchar' }, // 输入框
//   { name: '整数', id: 'int' }, // 数字输入框(不能输入小数)
//   { name: '小数', id: 'double' }, // 数字输入框
//   { name: '日期', id: 'date' }, // 日期选择框
//   { name: '日期时间', id: 'datetime' }, // 日期选择框
//   { name: '长整数', id: 'bigint' }, // 数字输入框(不能输入小数)
// ]);

const infoFormRefList = ref<any>(null); // form表单数组
const onSave = async () => {
  let flag = true;
  let list: any = [];
  formList.value.forEach((item: any, index: number) => {
    infoFormRefList.value[index].validate(async (valid: any) => {
      if (valid) {
        // item中有表单输入的值
        let columnList: any = [];
        for (let i = 0; i < item.rowNum; i++) {
          let obj: any = {};
          Object.keys(item.columnObj).forEach((key) => {
            // 取key的最后一位字符
            if (key.slice(-1) === String(i)) {
              let objKey = key.slice(0, key.length - 1); // 去掉key最后一位字符
              obj[objKey] = item.columnObj[key];
            }
          });
          columnList.push(obj);
        }
        let paramsObj: any = {};
        paramsObj.projectNo = route.query.projectNo || props.projectNo;
        paramsObj.pageId = item.pageId;
        paramsObj.columnList = columnList;
        list.push(paramsObj);
      } else {
        Message({
          message: `表单${formList.value[index].name}未通过校验,请检查`,
          type: 'warning',
        });
        flag = false;
      }
    });
  });
  //   传参格式如下
  //   {
  //     "formList": [
  //         {
  //             "projectNo": "123123",
  //             "pageId": 111,
  //             "columnList": [{
  //                 "id": 123,
  //                 "fill_user_name":111
  //                 ...
  //             }]
  //         }
  //     ]
  // }
  if (flag) {
    let params = { formList: list };
    await createOrUpdateForm(params);
    asyncGetFormInfo();
    return true;
  } else {
    return false;
  }
};

// 查询其他所有动态表单列表
const formList = ref<any>([]);
const getOtherInfo = async () => {
  const params = {
    projectNo: route.query.projectNo || props.projectNo,
    catalogCode: route.query.catalogCode || props.catalogCode,
  };
  const { data } = await getItemAttribute(params);

  formList.value = data.map((item: any) => {
    item = Object.assign({}, item, { columnObj: {} }); //(响应式赋值)
    item.rowNum = 1;
    return item;
  });

  getDictTypeListAndFormInfo();
};
// 查询其他所有动态表单内容以便反显
const asyncGetFormInfo = async () => {
  let list: any = [];
  formList.value.forEach((item: any) => {
    let obj: any = {};
    obj.projectNo = route.query.projectNo || props.projectNo;
    obj.pageId = item.pageId;
    list.push(obj);
  });
  const params = { formList: list };
  const { data } = await getFormInfo(params);
  formList.value = formList.value.map((item1: any) => {
    data.forEach((item2: any) => {
      if (item1.pageId === item2.pageId) {
        if (item2.formData?.length) {
          item1.rowNum = item2.formData.length;
          item2.formData.forEach((item3: any, index3: number) => {
            Object.keys(item3).forEach((key) => {
              item1.columnObj = Object.assign({}, item1.columnObj, { [`${key}${index3}`]: item3[key] }); //(响应式赋值)
            });
          });
        }
      }
    });

    return item1;
  });
  loading.value = false;
};

const getDictTypeListAndFormInfo = async () => {
  // 获取所有不为0的dictionarytypeId列表
  let idList: any = [];
  formList.value.forEach((item: any) => {
    if (item.attributetemplateList?.length) {
      idList.push(...item.attributetemplateList.map((it: any) => it.dictionarytypeId).filter((id: any) => id !== 0));
    }
  });

  let params = { ids: idList };
  const { data } = await findDictTypeListByTypeIds(params);
  formList.value.forEach((item1: any) => {
    if (item1.attributetemplateList?.length) {
      item1.attributetemplateList.forEach((item2: any) => {
        if (idList.indexOf(item2.dictionarytypeId) > -1) {
          // 在下拉框项时,把该下拉框的下拉内容赋值上去 // 工作组件的上线版本管理的预览有另一种方式
          item2.dictList = data.find((item3: any) => item3.dictType.id === item2.dictionarytypeId)?.dictList || [];
        }
      });
    }
  });
  asyncGetFormInfo();
};

const addRow = (item: any) => {
  item.rowNum++;
};

onMounted(() => {
  loading.value = true;
  getOtherInfo();
});
defineExpose({
  onSave,
});
</script>
<style lang="scss" scoped>
.otherFrom {
  display: flex;
  flex-direction: column;
  padding-top: 30px;
  background: #fff;
}

.title {
  font-size: 20px;
  font-weight: 500;
  color: #1c1c1c;
  line-height: 20px;

  .addBtn {
    font-size: 24px;
    margin-left: 10px;
    color: #333;
  }
}
</style>

二、遇到的问题与解决

1.循环的表单的modal要设置为动态的:   

  :model="item.columnObj"

绑定的值与当前的item有关。可以直接是item,但是后面的表单项的 v-model 的值必须与 :modal 的值只差一级。详见第二点。

2. 表单项的 v-model 的值必须与 :modal 的值要只差一级。

如果后者是item,则前者必须是item.xxxx。不能是item.xxx.xxx。否则 v-model 的值不能存进form的modal中,则必填校验无法完成。

3.表单相关对象的赋值时必须用 Object.assign

这样可以使得新增的对象具有响应式 见vue响应式赋值

   item = Object.assign({}, item, { columnObj: {} }); //(响应式赋值)

4. 省市区下拉框的v-modal的值是个对象时,要给初始值

省市区下拉框的v-modal的值是个对象时,要给初始值,否则选择时不能正确反显。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值