miyuesc / bpmn-process-designer

bpmn-js 工具库
https://juejin.cn/post/7304831120710434868
Apache License 2.0
1.62k stars 460 forks source link

MyProcessDesigner作为子组件引用使用时,动态修改其value prop时,PropertiesPenel下的ElementBaseInfo组件属性未及时同步 #10

Closed bevishwang closed 3 years ago

bevishwang commented 3 years ago

使用Vue Devtools修改App中ControlForm中的value属性,以使MyProcessDesigner组件重新渲染流程设计器 使用Vue Devtools修改App中ControlForm中的value属性 修改后的渲染结果出现多组件之间状态不一致的情况 修改后的渲染结果出现多组件状态不一致的情况

默认初始化的xml内容 `<?xml version="1.0" encoding="UTF-8"?>

` 通过Vue Devtool修改后的value值 ` ` 注意bpmn2:process元素中的name属性,经过调试,发现再次`this.bpmnModeler.importXML(xml)`后,似乎`window.bpmnInstances.bpmnElement` 未同步修改为新的对象。
miyuesc commented 3 years ago

好问题,我过两天看看

miyuesc commented 3 years ago

@hbh3819 你好,我尝试了一下,不适用devtools修改配置是正常的,window下对应的数据也会修改。

bevishwang commented 3 years ago

问题最初是在这样的场景中出现的:自定义的组件中使用了MyProcessDesigner组件,并针对其value绑定了一个动态属性,然后使用http请求获取服务端的xml,在http响应后修改MyProcessDesigner的value绑定的动态属性为请求的返回值,此时可能由于http请求是在MyProcessDesigner初始化完成后才响应的,引起了重新初始化,于是就会不时出现组件间属性不一致的情况。

经测试当把http请求的延迟控制在较大的范围时:比如1s,组件之间属性不一致的情况会稳定复现

您可以使用如下代码来模拟当http请求出现较大延迟时的效果,在App.vue的created生命周期函数中添加: setTimeout(() => { let newValue = '<?xml version="1.0" encoding="UTF-8"?>\n' + '<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" id="diagram_Process_1618884876252" targetNamespace="http://bpmn.io/schema/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">\n' + ' <bpmn2:process id="Process_1618884876252" name="修改名称后的业务流程_1618884876252" isExecutable="true" />\n' + ' <bpmndi:BPMNDiagram id="BPMNDiagram_1">\n' + ' <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1618884876252" />\n' + ' </bpmndi:BPMNDiagram>\n' + '</bpmn2:definitions>'; this.$set(this.controlForm, 'value', newValue) }, 1000);

miyuesc commented 3 years ago

你这个问题我大致清楚了,因为当初为了减少开销,本身 process-designer.vue 是没有监听 value 变化的。不然会导致一点小变动这边都会重新绘制流程图。 你可以在 请求 完成之后,重新调用 createNewDiagram(xml) 方法来主动刷新流程图

bevishwang commented 3 years ago

我看到process-designer.vue中的watch已经包含了针对value属性监听,并在其发生变化时重新绘制流程图呢 watch: { value: { handler: function(val) { this.createNewDiagram(val); } } }

问题似乎出现PropertiesPanel.vue中的getActiveElement方法中: getActiveElement() { // 初始第一个选中元素 bpmn:Process this.processElement = window.bpmnInstances.elementRegistry.find(el => el.type === "bpmn:Process"); this.initFormOnChanged(this.processElement); // 监听选择事件,修改当前激活的元素以及表单 this.bpmnModeler.on("selection.changed", ({ newSelection }) => { if (newSelection && newSelection.length) { const element = newSelection[0]; this.initFormOnChanged(element); } else { this.initFormOnChanged(this.processElement); } }); this.bpmnModeler.on("element.changed", ({ element }) => { // 保证 修改 "默认流转路径" 类似需要修改多个元素的事件发生的时候,更新表单的元素与原选中元素不一致。 if (element && element.id === this.elementId) { this.initFormOnChanged(element); } }); },

请注意其中的以下代码段: this.processElement = window.bpmnInstances.elementRegistry.find(el => el.type === "bpmn:Process"); this.initFormOnChanged(this.processElement); 由于该方法只会在PropertiesPanel.vue组件第一次创建时被调用,并由上述代码段设置当前bpmnModeler上下文,而后续重新绘制流程图时未被调用,从而导致bpmnModeler上下文未更新

miyuesc commented 3 years ago

你不说我都没注意这个watch还没删了。。。等会儿我看看吧

bevishwang commented 3 years ago

我本地通过修改PropertiesPanel.vue组件中的initModels和getActiveElement方法,并添加了reinitializeProcessElement方法,来暂时解决了这个问题。 主要思路为给bpmnModeler增加了一个'import.done'监听,当事件被触发时,重新执行ProcessPanel.vue的processElement初始化逻辑 不知是否可行? 代码如下:

    initModels() {
      // 初始化 modeler 以及其他 moddle
      if (!this.bpmnModeler) {
        // 避免加载时 流程图 并未加载完成
        this.timer = setTimeout(() => this.initModels(), 10);
        return;
      }
      if (this.timer) clearTimeout(this.timer);
      window.bpmnInstances = {
        modeler: this.bpmnModeler,
        modeling: this.bpmnModeler.get("modeling"),
        moddle: this.bpmnModeler.get("moddle"),
        eventBus: this.bpmnModeler.get("eventBus"),
        bpmnFactory: this.bpmnModeler.get("bpmnFactory"),
        elementRegistry: this.bpmnModeler.get("elementRegistry"),
        replace: this.bpmnModeler.get("replace"),
        selection: this.bpmnModeler.get("selection")
      };
      //修改开始
      this.bpmnModeler.on('import.done', event => this.reinitializeProcessElement())
      this.reinitializeProcessElement()
      this.getActiveElement();
      //修改开始
    },
    reinitializeProcessElement() {
      // 初始第一个选中元素 bpmn:Process
      this.processElement = window.bpmnInstances.elementRegistry.find(el => el.type === "bpmn:Process");
      this.initFormOnChanged(this.processElement);
    },
    getActiveElement() {
      //将原来此处的代码挪至reinitializeProcessElement方法中
      // 监听选择事件,修改当前激活的元素以及表单
      this.bpmnModeler.on("selection.changed", ({ newSelection }) => {
        if (newSelection && newSelection.length) {
          const element = newSelection[0];
          this.initFormOnChanged(element);
        } else {
          this.initFormOnChanged(this.processElement);
        }
      });
      this.bpmnModeler.on("element.changed", ({ element }) => {
        // 保证 修改 "默认流转路径" 类似需要修改多个元素的事件发生的时候,更新表单的元素与原选中元素不一致。
        if (element && element.id === this.elementId) {
          this.initFormOnChanged(element);
        }
      });
    },
miyuesc commented 3 years ago

你这个方式也是可以的

bevishwang commented 3 years ago

ok Thanks~