# vue和ext表单相互嵌套使用
# 背景
由于控制器是vue+ext双框架的项目,且存在很多老的组件是由ext实现的,在新页面的开发中如果要使用到就会很麻烦,并且使用vue重写的话成本巨大且无法保证覆盖全面,于是实现以下互相嵌套使用的方案,解决这一困扰点。
# vue表单中使用ext
之前已经实现过了单独调用ext弹窗的方案,这种比较独立,所以还是很好实现的。
另外存在的大场景就是combo类组件,表单中嵌入输入框,点击显示弹窗的那种。比如选择端口、交换机面板、各种设备的树形选择等等,都是用ext做的。
# 原理
这种做起来也简单,原理如下:
- vue提供个组件容器,并调用useExt将其转化为ext容器。
- 实例化传入的ext组件,挂载到容器上。
- 注册到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,去生成内部的独立表单系统,以做校验
该方案也存在一些缺陷,
- 校验错误的样式,每个组件都需要手动写css,太不方便了,这里修改为底层自动适配,使用者无需关心。
- useFormGroup改为了ixs的
- 提示语位置按照我们控制器视觉标准进行修改
# 使用
控制器中已添加对应的使用说明,这里简单给个示例:
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组件一样,实例化调用即可。
# 总结
通过以上方案:
- ext的已有的表单组件(交换机面板、各种设备选择器等)可以快速接入vue中,不再需要重写。
- 原ext页面中做改动,可以使用vue编写,降低开发难度,提高开发效果。