# vue和ext表单相互嵌套使用

# 背景

由于控制器是vue+ext双框架的项目,且存在很多老的组件是由ext实现的,在新页面的开发中如果要使用到就会很麻烦,并且使用vue重写的话成本巨大且无法保证覆盖全面,于是实现以下互相嵌套使用的方案,解决这一困扰点。

# vue表单中使用ext

之前已经实现过了单独调用ext弹窗的方案,这种比较独立,所以还是很好实现的。

另外存在的大场景就是combo类组件,表单中嵌入输入框,点击显示弹窗的那种。比如选择端口、交换机面板、各种设备的树形选择等等,都是用ext做的。

# 原理

这种做起来也简单,原理如下:

  1. vue提供个组件容器,并调用useExt将其转化为ext容器。
  2. 实例化传入的ext组件,挂载到容器上。
  3. 注册到Idux的表单系统中

首先调用idux的自定义表单

const { accessor, control: controlRef } = useAccessorAndControl();
useFormItemRegister(controlRef);

劫持ext的setJsonValue,在ext组件设置值时将值同步设置到idux表单中

extOptions.value.setJsonValue = function (..._args: SafeAny) {
  const [v] = _args;
  accessor.setValue(v);
  formItemValue.value = v;
  props.extComponent.prototype.setJsonValue.apply(this, _args);
};

在回显时,watch idux表单值的变化,同步设置给ext组件

watch(
  () => accessor.value,
  v => {
    if (v !== formItemValue.value) {
      containerRef.value?.setJsonValue(v);
    }
  }
);

通过以上几步即可将ext组件嵌入到vue表单中使用

其它的非表单组件也可参照该思想去实现。

# 使用

使用上遵循简单易用的原则

直接调用ExtComboToVue组件,传入ext组件的构造函数、所需配置,以及idux表单中的control,即可像使用vue组件一样轻松。

<template>
  <ExtComboToVue
    :control="props.control"
    :ext-component="SwitchTreeCombo"
    :ext-options="LogComboOptions" />
</template>

<script lang="ts" setup>
import { logTreeComboProps } from './types';
import '~/js/business/Switch/SwitchTreeCombo.js';
import { ExtComboToVue } from '@/components/ExtComboToVue';

const _ = window._;

const props = defineProps(logTreeComboProps);

const SwitchTreeCombo = B.Switch.SwitchTreeCombo;
const LogComboOptions = {
  xx: xx
};
</script>

# ext表单中使用vue

ext中使用vue相对就比较麻烦,这里参考了深信服两位同事林浩钦(数据嵌入ext表单)、余龙栋(vue组件渲染、事件)的实现。

# 原理

# vue组件渲染、事件

大致原理就是传入vue组件到一个Ext.BoxComponent构造函数中,在ext渲染时动态的渲染vue并挂载到节点上。(这种原理我们之前也实现过了,只是没有封装)

另外在ext中监听了vue的emit事件,并可对外暴露事件,达到数据交换的操作。

如果单纯的只是渲染,这个封装的函数还是能达到效果。

但是有个缺点就是没有嵌入到ext表单中,设置值、获取值都需要手动处理,十分的麻烦,所以需要接入以下方案

# 数据嵌入ext表单

这里做的操作就是在前面的基础上配置了setJsonValue,getJsonValue及validator,解决了表单值的获取、回显及校验

并且获取外层ext中配置的校验限制,传入到vue的props,在vue组件中我们需要调用给定的hooks,去生成内部的独立表单系统,以做校验

该方案也存在一些缺陷,

  1. 校验错误的样式,每个组件都需要手动写css,太不方便了,这里修改为底层自动适配,使用者无需关心。
  2. useFormGroup改为了ixs的
  3. 提示语位置按照我们控制器视觉标准进行修改

# 使用

控制器中已添加对应的使用说明,这里简单给个示例:

vue组件

<template>
  <IxsForm ref="formRef" :control="formGroupControl">
    <IxsFormItem :name="controlName" label="">
      <IxsSelect
        :data-source="ALARM_PUSH_METHOD"
        :placeholder="$i('global.select.dropdown')"
        :multiple="true"
        :control="controlName"
      />
    </IxsFormItem>
  </IxsForm>
</template>

<script lang="ts" setup>
import { useVueToExtField } from "@/utils/vueToExt";
import XX from "./Index.vue";
const props = withDefaults(
  defineProps<{
    allowBlank?: boolean;
    min?: number;
    max?: number;
    validators?: ValidatorFn[] | ValidatorFn;
  }>(),
  {
    allowBlank: true,
    min: 0,
    max: 0,
    validators: () => [],
  }
);
const emits = defineEmits<{
  (e: "change", value: DeviceItem[]): void;
}>();
const {
  formGroupControl,
  controlName,
  getValue,
  setValue,
  isValid,
  validate,
  clearInvalid,
  getInvalid,
} = useVueToExtField<DeviceItem[]>([], props, emits);
defineExpose({
  // 必须暴露的方法
  getValue,
  setValue,
  // 暴露校验相关的方法
  isValid,
  validate,
  clearInvalid,
  getInvalid,
});
</script>

生成ext组件

import { createVue2ExtField, createExtComponent } from '@/utils/vueToExt';

import DeviceSelectFormItem from '@/components/business/DeviceSelect/FormItem.vue';

const ExtComponent = createExtComponent(DeviceSelectFormItem, 'device_select');
const vue2ExtField = createVue2ExtField(ExtComponent);

const DeviceSelectField = Ext.extend(vue2ExtField, {
  // 重写设置vue组件的方法
  setPropsData() {
    const { allowBlank, min, max, validators, emptyText } = this;
    this.setData({
      allowBlank,
      min,
      max,
      validators,
      placeholder: emptyText,
      defaultWidth: this.getDefaultWidth(),
    });
  },
  // 重写当校验失败时qtip需要加到哪些dom上
  getNeedAddQtipDom() {
    return [
      document.querySelector(`.${this.getCls()} .ix-selector-input-inner`),
      document.querySelector(`.${this.getCls()} .ix-selector-overflow`),
    ].filter(Boolean);
  },
});

export default DeviceSelectField;

然后将生成的ext组件像往常普通的ext组件一样,实例化调用即可。

# 总结

通过以上方案:

  1. ext的已有的表单组件(交换机面板、各种设备选择器等)可以快速接入vue中,不再需要重写。
  2. 原ext页面中做改动,可以使用vue编写,降低开发难度,提高开发效果。