Skip to main content

自定义 dom

需求说明

基础能力

自定义 dom 是一种采用在线编程的方式提供使用者一种扩展现有平台组件的能力。

自定义 dom 组件的效果对标外部页面元件能力。

自定义 dom 对比外部页面的优缺点:

1、自定义 dom 需要使用原生 js 方式接入,外部页面可以使用 jQuery,vue, react 组件开发出来的页面或路由地址

2、自定义 dom 不需要单独起服务就可以直接使用,对于功能简单,功能点小的组件模块可以实现快速接入,预览及使用,快速上线;外部页面需要部署到服务器上,组件层需要配置服务器对应的页面地址才能使用。

3、自定义 dom 可以沉淀通用的 dom 模版,快速得到复制;外部页面是项目内部可以共享,只有维护到定制化项目模版里面才能给到其他新项目得到复用。

自定义 dom 组件使用 webComponent 组件实现,css,html 资源是互相隔离的。

AI 能力

借助雪浪共工提供的 AI 服务,在自定义 dom 组件写代码的地方,增加调用 chatgpt 的功能。

这里需要引导文档,用一个 case 向用户展示该如何与 AI 助手交互,得到理想效果。

原型

涉及资源

页面编辑器、逻辑

技术说明&使用说明

dom 名称: 每个 dom 资源的名称保证唯一性,dom 名称 + 随机数会作为 key 值组件配置:组件配置 html 代码,js 代码,css 代码,支持自动预览和手动预览,预览完成可以保存配置

后续可以沉淀模版,根据模版快速填充代码,对代码进行二次编辑使用。

组件配置说明:

自定义 dom 组件提供了 sdk 能力可以调用共工现有能力,或者与共工现有能力进行交互。

主要包含以下指令:

// 指令名称
export const COMMAND_NAME = {
OPEN_PAGE: 'OPEN_PAGE', // 打开页面
CLOSE_PAGE: 'CLOSE_PAGE', // 关闭页面
RESET_FORM: 'RESET_FORM', // 重置表单
COMMON_ACTION: 'COMMON_ACTION', // commonAction
CALL_MICROFLOW: 'CALL_MICROFLOW', // 微流调用
CONTEXT_DATA: 'CONTEXT_DATA', // 上下文数据
SHOW_MESSAGE: 'SHOW_MESSAGE', // 提示信息
REFRESH_PAGE: 'REFRESH_PAGE', // 刷新页面
GET_PAGEPKID: 'GET_PAGEPKID', // 获取页面主键id
UPLOAD_FILE: 'UPLOAD_FILE' // 文件上传
};
注意事项:

由于自定义 dom 组件使用的是 webComponent 组件使用,默认开启 shandow Dom 渲染,内部元素和外部元素是隔离的,组件内容获取 dom 元素需要使用到 shadowRoot 对象,不能使用 document 元素

js 编辑器自动暴露:

shadowRoot 当前 html 顶层 dom 元素,可以通过 shadowRoot.querySelector 获取到当前 html 编辑器中配置的 dom 元素callCmd 调用 sdk 指令的方法,调用方法具体看下面的文章,会具体讲解
自定义 SDK 调用

例如: showMessage 提示信息,使用共工系统的提示信息

callCmd(COMMAND_NAME.SHOW_MESSAGE, { content: '保存成功', type: 'success' });

打开页面:

callCmd(COMMAND_NAME.OPEN_PAGE, {
resourceName: '测试弹出框',
params: {
test: 1
},
}, (res) => {
console.log('==res=', res);
})
COMMAND_NAME 参考上面的指令定义loadSource 加载动态资源模块,支持 shadow DOM 内部加载和全局加载 2 种
export type SourceItem = {
url: string; // 资源地址
type: 'css' | 'script'; // 资源类型
global?: boolean; // 是否全局加载
};

例如 iview 组件库,vue2 版本包加载

loadResource([
{url: 'https://unpkg.com/vue@2.7.14/dist/vue.js', type: 'script'},
{url: '//unpkg.com/iview/dist/styles/iview.css', type: 'css', global: true},
{url: '//unpkg.com/iview/dist/styles/iview.css', type: 'css'},
{url: '//unpkg.com/iview/dist/iview.min.js', type: 'script'}
]).then((res) => {
new Vue({
el: shadowRoot.querySelector('#domContainer'),
data: {
visible: false
},
methods: {
show: function () {
this.visible = true;
}
}
})
})
调用 SDK 提示消息

js 代码编辑器内部需要添加以下代码:

// 提示信息
const showMessage = () => {
callCmd(COMMAND_NAME.SHOW_MESSAGE, { content: '保存成功', type: 'success' });
}
// 找到对应的dom节点
const showMsgBtn = shadowRoot.querySelector('button#showMsg');
showMsgBtn.addEventListener('click', (event) => {
showMessage();
});

效果:

调用 SDK 打开页面

前提:需要配置页面资源

js 代码编辑器内部需要添加以下代码:

<html>
<body>
<div class="content">
<span>iview组件测试页</span>
<div id="domContainer">
<i-button @click="show">Click me!</i-button>
<i-button @click="openPage">打开页面</i-button>
<Modal v-model="visible" title="Welcome">欢迎使用 iView</Modal>
</div>
</div>
</body>
</html>
/** 打开弹窗 */
const openUserPage = () => {
callCmd(COMMAND_NAME.OPEN_PAGE, {
resourceName: 'testPage',
params: {
test: 1
},
}, (res) => {
console.log('==res=', res);
})
}

loadResource([
{url: 'https://unpkg.com/vue@2.7.14/dist/vue.js', type: 'script'},
{url: '//unpkg.com/iview/dist/styles/iview.css', type: 'css', global: true},
{url: '//unpkg.com/iview/dist/styles/iview.css', type: 'css'},
{url: '//unpkg.com/iview/dist/iview.min.js', type: 'script'}
]).then((res) => {
new Vue({
el: shadowRoot.querySelector('#domContainer'),
data: {
visible: false
},
methods: {
show: function () {
this.visible = true;
},
openPage: function() {
openUserPage()
}
}
})
})

效果:

调用 SDK 请求共工逻辑

####### 逻辑调用传递对象数据

事例:

调用逻辑入参 params 属性,可以传递 paramKeyMap 参数,或者 paramList 参数

2 者的区别可以参考外部页面组件,paramKeyMap 可以以 <key,value> 的方式传递 map 数据;paramList 是传递函数传参的方式传值,不知道入参名称,只知道入参类型和入参顺序。

// 逻辑对象入参
// 获取到当前的dom实例
const objectParamBtn = shadowRoot.querySelector('button#handleObjectParam');
if(objectParamBtn) {
// dom元素绑定点击事件
objectParamBtn.addEventListener('click', (event) => {
event.preventDefault();
// 调用逻辑
callCmd(COMMAND_NAME.CALL_MICROFLOW, {
resourceName: 'objectParam',
params: {
paramKeyMap: {
// 配置paramKeyMap参数,传递的数据只需要知道传参的key值和value值
param: {
name: '张三',
age: 20,
sex: 'male'
}
}
}
},
(res) => {
console.log('commonCall', res);
});
});
}

####### 逻辑调用传递普通类型数据

事例:

// 逻辑普通类型数据入参
const handleCommonParam = shadowRoot.querySelector('button#handleCommonParam');
if(handleCommonParam) {
handleCommonParam.addEventListener('click', (event) => {
event.preventDefault();
// 调用逻辑
callCmd(COMMAND_NAME.CALL_MICROFLOW, {
resourceName: 'commonParam',
params: {
paramKeyMap: {
name: '张三',
age: 20,
}
}
},
(res) => {
console.log('commonCall', res);
});
});
}

####### 逻辑调用传递文件数据

事例:

文件上传的时候需要直接在 params 里面直接传递字段信息,以 <key, value> map 数据方式配置入参。(注意: 传递文件的方式不需要再配置 paramKeyMap 或者 paramList)

// 逻辑文件上传
const handleUploadFile = shadowRoot.querySelector('button#handleUploadFile');
if(handleUploadFile) {
handleUploadFile.addEventListener('click', (event) => {
event.preventDefault();
const file = shadowRoot.querySelector('#file1');
console.log('file', file.files[0]);
// 调用逻辑
callCmd(COMMAND_NAME.CALL_MICROFLOW, {
resourceName: 'uploadFile',
params: {
name: file.files[0].name,
file: file.files[0]
}
},
(res) => {
console.log('commonCall', res);
});
});
}

逻辑调用效果:

动态加载资源

我们可以基于第三方的资源库,帮助我们完成一部分功能

例子 1:

我们引入 vue2, iview, 使用 vue2 的方式实现业务功能

<html>
<body>
<div class="content">
<span>iview组件测试页</span>
<div id="domContainer">
<i-button @click="show">Click me!</i-button>
<i-button @click="openPage">打开页面</i-button>
<Modal v-model="visible" title="Welcome">欢迎使用 iView</Modal>
</div>
</div>
</body>
</html>
/** 打开弹窗 */
const openUserPage = () => {
callCmd(COMMAND_NAME.OPEN_PAGE, {
resourceName: 'testPage',
params: {
test: 1
},
}, (res) => {
console.log('==res=', res);
})
}

loadResource([
{url: 'https://unpkg.com/vue@2.7.14/dist/vue.js', type: 'script'},
{url: '//unpkg.com/iview/dist/styles/iview.css', type: 'css', global: true},
{url: '//unpkg.com/iview/dist/styles/iview.css', type: 'css'},
{url: '//unpkg.com/iview/dist/iview.min.js', type: 'script'}
]).then((res) => {
new Vue({
el: shadowRoot.querySelector('#domContainer'),
data: {
visible: false
},
methods: {
show: function () {
this.visible = true;
},
openPage: function() {
openUserPage()
}
}
})
})

效果展示:

例子 2:

我们引入 qrcodejs, 实现二维码生成

<html>
<body>
<div class="content">
<span>二维码生成</span>
<div id="qrcode"></div>
</div>
</body>
</html>
loadResource([
{url: 'https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js', type: 'script'}
]).then((res) => {
console.log('QRCode', QRCode);
var qrcode = new QRCode(shadowRoot.getElementById("qrcode"), {
text: "test content",
width: 128,
height: 128,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
});
})

展示效果:

属性配置说明
是否预览:如果当前组件配置有更新,可以通过切换当前配置实现刷新。配置参数:可以配置逻辑,页面,若未配置,则无权限调用。传参配置:用户可以设置给外部页面设置传参,传参获取方式可在下面介绍。

动态传参数据:

场景:自定义 dom 组件内部需要获取上层数据容器的数据做一些数据处理,那就需要设置动态传参,将上层的数据视图数据通过表达式传递到自动 dom 元素内部使用,也可以通过事件实时监听数据的变化

上面的案例是外层配置了数据视图,数据视图有 2 个输入框,1 个是姓名,1 个是年龄,实时输入内容,文本展示组件会实时更新,dom 组件可以接收到第一次值,后续要持续接收需要使用事件监听方式实时监听数据变化。

js 配置如下:

这里除了使用到 window.$dom?.props?.getPropsData 获取到上下文传递过来的数据,还使用到的 bus 事件,dom 名称:updateProps 事件

console.log('当前传递数据', window.$dom?.props?.getPropsData?.());
function updateProps(data) {
console.log('===数据更新===', data);
shadowRoot.querySelector('#result').innerHTML=`${data.master.name ? '姓名:'+ data.master.name : ''}${data.master.age ? '年龄:'+ data.master.age : ''}`
}
const applicationName = window?.$dom?.props?.name || 'test';
// 先销毁监听
window.$dom?.bus.$off(`${applicationName}:updateProps`, updateProps);
// 监测上层数据变化
window.$dom?.bus.$on(`${applicationName}:updateProps`, updateProps);

效果展示:

dom 交互事件
updateProps 事件

说明:动态数据配置的依赖数据发生变化,会触发当前事件的执行,只有自定义 dom 组件内部监听了当前事件,才能处理到对应的事件

参考事例:

function updateProps(data) {
console.log('===数据更新===', data);
shadowRoot.querySelector('#result').innerHTML=`${data.master.name ? '姓名:'+ data.master.name : ''}${data.master.age ? '年龄:'+ data.master.age : ''}`
}
const applicationName = window?.$dom?.props?.name || 'test';
// 先销毁监听
window.$dom?.bus.$off(`${applicationName}:updateProps`, updateProps);
// 监测上层数据变化
window.$dom?.bus.$on(`${applicationName}:updateProps`, updateProps);

效果展示:

reoload 事件

说明:逻辑调用配置了刷新功能,会触发页面级刷新或者页面中的组件级更新,自定义 dom 也支持刷新操作

参考事例:

function reloadDom(data) {
console.log('===数据更新===', data);
}
// 先销毁监听
window.$dom?.bus.$off(`${applicationName}:reload`, reloadDom);
window.$dom?.bus.$on(`${applicationName}:reload`, reloadDom);

效果展示:

activated 事件、deActivated 事件 (组件切入切出事件)

说明:缓存场景下,导航切换到当前页面展示,当前页面包含到自定义 dom 组件,自定义 dom 组件监听 activated 的事件,则会被调用。

参考事例:

const applicationName = window?.$dom?.props?.name || 'test';
const domActivated = () =>{
console.log(applicationName+'===dom Activated===');
}
const domDeActivated = () =>{
console.log(applicationName+'===dom deActivated===');
}
// 先销毁监听
window.$dom?.bus.$off(`${applicationName}:activated`, domActivated);
window.$dom?.bus.$off(`${applicationName}:deActivated`, domDeActivated);
// 监测上层数据变化
window.$dom?.bus.$on(`${applicationName}:activated`, domActivated);
window.$dom?.bus.$on(`${applicationName}:deActivated`, domDeActivated);

效果展示: