基于vue+element的动态表单的设计与实现(2)

5 篇文章 0 订阅

动态表单的设计与实现


备注:细节可能有误,主要提供思路

接着上一篇,这篇主要讲解字段监听和控制相关内容。

设计模式

在这之前,需我们要对观察者模式和发布订阅模式有一定的认识。以下两张图直观的的描述了二者的区别。这里需要深入了解的话,移步度娘,相关的文章很多。

字段的监听

目的:在业务代码层,实时监听某个字段内容的改变。

实现方案:在模板解析的完成后,为需要监听的字段注册一个回调函数,并将该字段信息及回调函数收集起来,全部分发到底层的表单单元上。在底层表单单元渲染时,判断自身是否在收集的信息中,若存在,当值改变时,调用该回调函数。

首先,我们需要注册一个全局变量,注入到每个分组和表单单元中。
修改form-format.js

 // 新增函数,用于获取字段的索引,如["formData", "basic", "name"]
 function setIndex(parents = [], key = 'formData') {
  const index = [...parents];
  index.push(key);
  return index;
}
function format(list = []) {
  if (!Array.isArray(list)) {
    return [];
  }
  const rootIndex = setIndex();
  const $listen = {
    $actionPublish: {}, // 动态响应的发布,这个用于收集监听字段时注册的字段及函数
  };
  const tplList = list.map((i) => {
    // const groupItem = { ...i };
    // 把索引和$listen一起放进来
  	const groupItem = {
      ...i,
      $index: setIndex(rootIndex, i.groupNo),
      $listen,
    };
    ...
  });
  // 同理修改handleFormFieldList
  function handleFormFieldList() {
  	// const fieldItem = { ...o };
  	const fieldItem = {
      ...o,
      $index: setIndex(groupItem.$index, o.fieldId),
      $listen,
    };
  }

在动态表单入口Index.vue中,添加以下代码

	formatTpls() {
	  const { $listen, tplList } = formFormat.call(this, this.formConfig);
      this.$listen = $listen;
      this.tplList = tplList;
      // actionPublishConfig是业务代码层传下来的培配置
      // 这里先默认为
      // [{
      //   fieldKey: "formData.basic.name",
      //   cb: this.handleNameChange // 业务代码中的函数
      // }]
      this.actionPublishConfig.forEach(item => {
        this.registerActionPublish(item.fieldKey, (val) => {
          item.cb(val);
          return true;
        });
      });
	}
	/**
	 * 新增该函数
     * 注册监听动态发布
     */
    registerActionPublish(key, cb) {
      if (!this.$listen) {
        return;
      }
      const { $actionPublish } = this.$listen;
      if (!$actionPublish[key]) {
        $actionPublish[key] = [];
      }
      $actionPublish[key].push(cb);
    },

到这里,已经把监听的字段及回调注册到每一个表单单元中了,接下来修改表单单元,打开FormItem.vue,添加以下代码:

mounted() {
  this.init();
  // 新增initWatchValue
  this.initWatchValue();
},
watch: {
  inpValue: {
    handler(val) {
      // 新增hanldeInpValueChange
      this.hanldeInpValueChange();
      this.$emit("input", val);
    },
    immediate: true,
  },
},
methods: {
  init() {
  	...
  },
  initWatchValue() {
    const { $index, $listen } = this.item;
    const currentFieldIndex = $index.join(".");
    if (!$listen.$actionPublish[currentFieldIndex]) {
      this.$actionPublish = null;
      return;
    }
    this.$actionPublish = $listen.$actionPublish[currentFieldIndex];
  },
  hanldeInpValueChange() {
    if (Array.isArray(this.$actionPublish)) {
      this.$actionPublish.forEach(() => {
      	f(this.inpValue, this);
      })
    }
  }
}

这样就实现了字段内容改变的实时监听,避免了深度watch的开销。

字段状态的控制

这里主要用到发布订阅的设计模式。
修改表单的配置信息,增加控制的逻辑配置,以第一篇中的配置为基准,添加control的信息:

{
        component: "el-select",
        fieldId: "country",
        name: "国家",
        ...
        control: [
          {
            value: "CN",
            fields: ["formData.basic.name"]
          },
          {
            value: "US",
            fields: ["formData.basic.name"]
          },
          {
            value: "US",
            fields: ["formData.basic.sex"]
          }
        ]
      }

修改form-format.js,添加发布者、订阅者:

function format(list = []) {
  ...
  const $listen = {
    $publish: {},       // 发布者
    $subscribe: {},     // 订阅者
    $actionPublish: {}, // 动态响应的发布
  };
  // 调用handleFormFieldList时,传入$listen
    const {
      fields,
      formData,
      rules,
    } = handleFormFieldList.call(this, i.fields, groupItem, $listen);
  ...
}
function handleFormFieldList(originFields, groupItem, $listen) {
  ...
  const fields = originFields.map((o) => {
    ...
    // 添加以下代码
    if (o.control) {
      // 初始化相应的发布者
      $listen.$publish[o.fieldId] = [];
      o.control.forEach((i) => {
        const { fields, value } = i;
        Array.isArray(fields) && fields.forEach(field => {
          // 初始化相应的订阅者
          if (!$listen.$subscribe[field]) {
            $listen.$subscribe[field] = [];
          }
          $listen.$subscribe[field].push({
			...o,
			targetVal: value, // 目标值
		  });
        });
      });
      // 为每个单元注册一个函数,用于通知订阅者执行相关函数
      fieldItem.$$watchValue = (value) => {
        const fieldId = o.fieldId;
        if ($listen.$publish[fieldId]) {
          const map = {};
          $listen.$publish[fieldId].forEach(({ fn, index }) => {
            // fn(value) 的返回值是对应组件的editStatus
            // 当发布者中的订阅函数未执行,或执行结果为true时,继续执行该其他订阅
            // 即:当执行到为false时,不执行其余函数
            // 目的是为了解决多个字段控制同一个字段时的互斥问题
            if (map[index] === undefined || map[index] === true) {
              map[index] = fn(value);
            }
          });
        }
      }):
    }
  });
}

ok,准备工作完成,下面回到FormItem.vue中,修改如下:

watch: {
  inpValue: {
   handler(val) {
     // 新增
     // 当改变值时,调用form-format中注册的函数,通知相应的发布者执行订阅
     if (this.item.$$watchValue) {
       this.item.$$watchValue(this.inpValue);
     }
     ...
   },
   immediate: true,
 },
},
mounted() {
  this.init();
  // 新增handleSubscribe
  this.handleSubscribe();
  this.initWatchValue();
},
// 新增
beforeDestroy() {
  this.removePublish();
},
methods: {
 /**
   * 处理订阅
   */
  handleSubscribe() {
    const { $index, $listen, fieldId } = this.item;
    const index = $index.join(".");
    const subscribe = $listen.$subscribe[index];
    if (!subscribe) {
      return;
    }
    // 当前字段的值,控制其他字段不可编辑
    subscribe.forEach((i) => {
      let publisher = $listen.$publish[i.fieldId];
      const fn = (value) => {
        if (Array.isArray(value)) {
          // 多选的处理
          this.editStatus = !value.includes(i.targetVal);
        } else {
          this.editStatus = !(i.targetVal === value);
        }
        return this.editStatus;
      };
      // 将发布者和订阅者关联
      publisher && publisher.push({ fn, index });
    });
  },
  /**
   * 从发布者中移除订阅
   */
  removePublish() {
    const { $listen, fieldId } = this.item;
    const { $publish } = $listen;
    const key = `${fieldId}`;
    if ($publish[key]) {
      delete $publish[key];
    }
  },
}

到此,字段的监听和控制基本完成。下一篇进行表单模式的完善。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值