# E10 组件化 JS-SDK 说明文档

> 最低基线版本：10.0.2404.01


> ⚠️ 注意： SDK 只是对外部系统开放一个前端口子，允许接入 OA 内的业务组件功能，但是考虑到OA系统内部功能的复杂多样性，这里并不能保证所有功能均能够在三方系统中正常加载，如果遇到有不兼容的情况，需要提供二开流程单独兼容支持！


## 概述
E10 JS-SDK是 E10 面向外部网页前端系统提供的基于 E10 内部的前端工具库。通过使用 E10 JS-SDK，外部网页前端系统可以引入 E10 内部的一些组件，嵌入集成到自己前端页面中，在非E10页面中，使用E10内部的流程组件、EB组件、IM消息组件等。

## JS-SDK 使用步骤

### 1、前端资源配置
配置 Nginx 指向 OA 的前端地址（走代理，或者放自身服务器）需要将 OA 系统前端资源（/build目录）全部都放到自身服务器项目根目录的 /build 目录下，如果使用 Nginx 代理，那么需要在 Nginx  配置中将 /build 路径全部都指定到前端资源的 /build 目录。

### 2、script 加载必要的前端资源
在项目的 html 中，以 script 引入 vendor、utils 两个库的 js 文件，代码参考如下：
```html
<script src="/build/vendor/static/js/lib.js?v=92b07cf3"></script>
<script src="/build/utils/static/js/lib.js?v=f2b6d5f4"></script>
```

#### 特别说明
由于前端资源文件数量太多，前端资源可以直接跨域去访问E10域名下的前端资源文件，避免在当前环境下再部署一整套E10前端资源。在 html 中，调整引用路径，加上域名前缀。
```html
<script src="https://oa.customer.com.cn/build/vendor/static/js/lib.js?v=92b07cf3"></script>
<script src="https://oa.customer.com.cn/build/utils/static/js/lib.js?v=f2b6d5f4"></script>
```
并且在 init 初始化的时候设置 staticDomain 属性：
```html
weaverSDK.init({
  staticDomain: 'https://oa.customer.com.cn', // OA 系统域名地址
});
```
> E10 前端会占用window全局的 React、ReactDOM、ReactRouterDOM、mobx 等变量名，所以引入 E10 前端 js 时候要注意这一点，避免和自己系统中的变量有冲突。

#### 动态加载静态资源
如果不想将 js 引入直接写在 html 中，可以参考下面方法，动态引入 js 资源，只需要传入 OA 域名地址即可
```js
function loadSDK (domain, callback) {
  if (window.define && window.define.amd) {
    // 针对修复 AMD 规范下 OA 加载异常问题
    var originDefine = window.define;
    window.define = function (e, f, t) {
      const currScript = document.currentScript;
      var patt = /\/build\/([^\/]*)\/static\/js(_full)?\/[^\/]*\.js/;
      if (currScript && currScript.src && patt.test(currScript.src)) {
        var matched = currScript.src.match(patt);
        if (typeof f === 'function' && matched) {
          var moduleName = matched[1];
          if (moduleName === 'ckeditor4') {
            // 特殊处理 CK
            moduleName = 'Ckeditor4';
          } else if (window.weappUtils && window.weappUtils.camelcase) {
            moduleName = window.weappUtils.camelcase('weapp-' + moduleName);
          } else {
            moduleName = 'weapp' + String(moduleName[0]).toUpperCase() + moduleName.slice(1);
          }
          window[moduleName] = f.apply(window, (e || []).map(i => i === 'exports' ? window : window[i]));
        }
      } else {
        originDefine(e, f, t);
      }
    }
    Object.keys(originDefine).forEach(key => window.define[key] = originDefine[key]);
  }
  window.publicDomainapi = 'https://xxx.xxxxx.com.cn'; // 处理 api 接口二级域名
  window.publicDomainstatic = 'https://xxx.xxxxx.com.cn'; // 处理静态资源二级域名
  // 处理sdk文件加载
  var vScript = document.createElement('script');
  vScript.src = (domain || "") + "/build/vendor/static/js/lib.js";
  vScript.onload = function () {
    var uScript = document.createElement('script');
    uScript.src = (domain || "") + "/build/utils/static/js/lib.js";
    uScript.onload = function() {
      if (typeof callback === 'function') {
        callback();
      }
    }
    document.body.appendChild(uScript);
  }
  document.body.appendChild(vScript);
}
```
如何调用：
```js
loadSDK('https://oa.domain.cn', function () {
	window.weaverSDK.init(...);
});
```

### 3、通过 init 初始化 E10 配置
所有需要使用JS-SDK的页面必须先注入配置信息，否则将无法调用

```javascript
weaverSDK.init({
  oadomain: 'https://xxx.xxxxx.com.cn', // 必填，OA 系统域名地址
  transferApi: '/api/weaver/proxy', // api 中转接口
});
```

init 配置参数说明：
|名称|描述|类型|是否必填|默认值|基线版本|
| - | - | - | - | - | - |
| oadomain | OA 系统域名地址 | string | 是 | - | 10.0.2404.01 |
| oatenantkey | OA 租户的key | string | 否 | - | 10.0.2404.01 |
| authTokenApi | 获取认证 token 的 api 接口 | string | 否 | - | 10.0.2404.01 |
| authTokenRequest | 获取认证 token 的钩子函数 | () => Promise<{ status: boolean, data: { token: string } }> | 否 | - | 10.0.2404.01 |
| transferApi | 设置 E10 内部接口地址统一的中转服务地址 | string | 否 | - | 10.0.2404.01 |
| apiDomain | 后端接口跨域请求域名地址 | string | 否 | - | 10.0.2404.01 |
| staticDomain | 前端静态资源跨域访问域名地址 | string | 否 | - | 10.0.2404.01 |
| prefixApiPath | 设置 E10 内部接口地址统一的前缀 | string | 否 | - | 10.0.2404.01 |
| enableEcode | 启用Ecode，会去加载ecode文件 | boolean | 否 | false | 10.0.2404.01 |
| eteamsid | 当前登录用户的 eteamsid | string | 否 | - | 10.0.2404.01 |
| withCredentials | 跨域接口开启 withCredentials | boolean | 否 | false | 10.0.2404.01 |

### 4、登录认证配置

#### 方案一：对接集成统一认证

> 使用限制：该方案要求客户对接过集成统一认证平台，并且当前客户系统的域名和E10系统必须要一级域名相同。
> <br/>如需对接统一认证功能可以参考文档：https://www.e-cology.com.cn/sp/doc/docDetail/100500240009664421
> <br/><i style="color:red">此方案业务系统服务端不需要做任何开发！</i>

对于目前对接过统一认证功能的客户，只需要将OA地址（oadomain）和OA租户的key（oatenantkey）在init初始化时传入，当用户当前已经通过统一认证登录，init内部会自动获取用户认证信息。

使用示例：
```js
weaverSDK.init({
  oadomain: 'https://oa.customer.com.cn', // 必填，OA 系统域名地址
  oatenantkey: 'abcdefg12345678', // OA 租户key
});
```

##### 特别说明：
**如何获取 oatenantkey？** 在统一认证接入管理中，回调地址中的tk参数就是 OA 租户的key
![统一认证图示.png](/api/ecode/resource/view/${appRes}/1693792980-统一认证图示.png)


#### 方案二：对接开发平台Token接口

> 使用限制：客户环境后端能够提供 usercode （和E10同步过组织人员信息）
> 针对场景：业务系统未接入统一认证


##### 服务端对接

> <i style="color:red">最低基线版本：10.0.2406.01</i>

需要在客户系统的后端实现一个 token 接口，该接口中将客户当前的人员信息调用开发平台的认证接口，转换为 OA 系统的 eteamsid，并其返回给前端即可。

开放平台认证接口调用使用说明可以参考文档：https://weapp.eteams.cn/build/techdoc/wdoc/index.html#/public/doc/d330d83e-e357-4c25-ac22-e41897aa787e


> 前端这边接收的格式是 { status: true, data: { eteamsid: 'xxxx' } }，请实现的 token 接口按照该格式返回结果


##### 前端对接

在调用 weaverSDK.init 时传入服务端定义好的 token 接口地址

使用示例：
```js
weaverSDK.init({
  authTokenApi: '/api/ec/weaver/token', // 客户系统中实现的 token 接口地址
});
```

考虑到调用 token 接口，可能需要携带当前环境的会话信息，不同三方系统中接口的调用处理方式也不一样，这里还提供了 authTokenRequest 钩子函数方式，方便用户自行调用 token 接口，只要最终返回 token 数据即可

```js
weaverSDK.init({
  authTokenRequest: function () {
    // 三方系统自行调用 token 获取的逻辑
    // 钩子函数需要返回一个 Promise，并且返回值的数据格式类似于：
    // { status: true, data: { eteamsid: 'xxxx' } }
    // eteamsid 字段就是OA这边用户会话的 id 值
    return Promise.resolve({
      status: true,
      data: { eteamsid: 'xxxx' },
    });
  },
});
```

### 5、后端接口配置

#### 方案一：跨域访问方式

> 使用限制：能跨域访问到 E10 系统

只需要在 init的使用，传入`apiDomain`配置上E10系统的域名地址即可，使用示例：
```js
weaverSDK.init({
  apiDomain: 'https://xxx.xxxxx.com.cn', // E10系统域名地址
  // ...
});
```
> <i style="color:red">如果业务系统和E10网络隔离，则可以增加一个nginx/f5，用来打通网络，即业务系统请求E10的静态资源、接口地址统一修改为nginx/f5地址，做一个请求转发</i>

#### 方案二：中转接口方式
目前支持配置统一中转服务，将 E10 组件内部请求的地址统一通过该地址转发到 OA 系统。
只需要在init的时候，传入`transferApi`即可，使用示例：
```js
weaverSDK.init({
  transferApi: '/api/weaver/proxy', // api 中转接口
  // ...
});
```
注：如何实现使用SDK实现中转服务可以参考[文档](https://weaverecode.feishu.cn/docx/NhVEdP7iEo7ye9xdwxCc3GVPnib)。
> ⚠️ 注意：
中转服务方案不能拦截到所有的接口请求，一些接口未拦截到会走不到中转服务，目前已知的场景有：
1、图片预览重定向地址

#### 方案三：Nginx 配置代理方式

> 使用限制：当前客户系统是通过 Nginx 方式配置的服务

对于E10 组件内的接口可以通过Nginx代理配置，将这些接口代理到 OA 服务上，标准 E10 接口的前缀统一为 /api 和 papi 。
Nginx 配置可以参考：
```nginx
set $oa_server https://oa.customer.com.cn;
location /api {
	# 代理到 OA 服务
	proxy_pass $oa_server;
}
location /papi {
	# 代理到 OA 服务
	proxy_pass $oa_server;
}
```
如果 /api 和 papi 和自己接口的地址冲突了，可以在 init 时候设置 E10 接口统一的前缀，使用示例：
```javascript
weaverSDK.init({
  prefixApiPath: '/weaver', // api 前缀
  // ...
});
```
然后，Nginx 通过前缀+ /api 和 papi ，匹配地址，Nginx 配置可以参考：
```nginx
set $oa_server https://oa.customer.com.cn;
location /weaver/api {
	# 重写请求地址
	rewrite ^/weaver/api/(.*)$ /api/$1 break;
	# 代理到 OA 服务
	proxy_pass $oa_server;
}
location /weaver/papi {
	# 重写请求地址
	rewrite ^/weaver/papi/(.*)$ /api/$1 break;
	# 代理到 OA 服务
	proxy_pass $oa_server;
}
```

### 6、在 ready 中处理成功初始化
```javascript
weaverSDK.ready(function () {
  // init 初始化成功后会执行 ready 方法
  // 所有组件渲染必须执行在 init 初始化成功之后
});
```

### 7、在 error 中处理初始化失败
```javascript
weaverSDK.error(function () {
  // init 初始化失败会执行 error 方法
});
```

## 常见问题

### 1. 请求 baseTeams 接口的域名地址不对，导致接口报错

问题现象：

![](/api/ecode/resource/view/${appRes}/1716886560-image.png)

问题说明：

调用 weaverSDK.init 时，没有配置 apiDomain，导致接口没有转发到 OA 的域名下

解决方案：

调用 weaverSDK.init 时，需要配置下 apiDomain，参考示例：

```js
weaverSDK.init({
  apiDomain: 'https://xxx.xxxxx.com.cn', // E10系统域名地址
  // ...
});
```

### 2. 资源请求 CORS 跨域报错

问题现象：

![](/api/ecode/resource/view/${appRes}/1716886675-image.png)

问题说明：

访问 OA 下的资源时，提示 header 中没有加上 Access-Control-Allow-Origin，导致跨域报错

解决方案：

需要调整 OA 服务的 ng，给资源请求加上 CORS 跨域允许


### 3. 三方里请求 OA 接口 404 报错

问题现象：

![](/api/ecode/resource/view/${appRes}/1716953702-image2.png)

在三方系统中请求 OA 接口 404 报错

![](/api/ecode/resource/view/${appRes}/1716953715-image3.png)

在 OA 环境中访问该接口是正常的

![](/api/ecode/resource/view/${appRes}/1716953467-image.png)


问题说明：

OA 系统中开启了安全拦截，会拦截从外部发起到 OA 的访问请求

![](/api/ecode/resource/view/${appRes}/1716954042-image4.png)


解决方案：

关闭掉系统的安全拦截

![](/api/ecode/resource/view/${appRes}/1716954132-image6.png)

## JS-SDK 通用 Api 说明

### 1、组件渲染 weaverSDK.compRender

用途：用于渲染 E10 项目内的组件

参数说明：

|名称|描述|类型|是否必填|默认值|基线版本|
| - | - | - | - | - | - |
| compName | 渲染的组件的名称 | string | 是 | - | 10.0.2404.01 |
| container | 组件渲染到当前系统的位置，需要传入一个 HTMLElement 元素 | HTMLElement | 是 | - | 10.0.2404.01 |
| params | 组件渲染需要传入的参数 | Object | 否 | - |  10.0.2404.01 |

用法示例：
```javascript
weaverSDK.ready(() {
  weaverSDK.compRender({
  	compName: "deigners.EBComView", // 组件名称
  	container: dom, // 组件渲染插入的位置
  	params: { // 调用组件的参数
  		pageId: "892725187490283521",
      comId: "e5f4aba44cc0414c9a2394c0c9e02d71",
  	}
  }).then((success) => {
  	// 成功回调
  }, (failed) => {
  	// 失败回调
  });
});
```

### 2、函数调用 weaverSDK.funcApply
用途：用于调用 E10 项目内的一些函数
参数说明：
1. `funcName` 调用函数的名称
2. `params` 调用函数需要传入的参数

用法示例：
```javascript
weaverSDK.ready(() {
  window.weaverSDK.funcApply({
    funcName: 'em.addEmWebBtn',
    params: {
    },
  }).then((success) => {
  	// 成功回调
  }, (failed) => {
  	// 失败回调
  });
});

```

## E10 组件使用说明

### 1、日程组件使用说明
通过`weaverSDK.compRender`调用日程组件
- `compName` 组件名称：`designer.EBComView`
- `params` 参数说明：

|名称|描述|类型|是否必填|
| - | - | - | - |
|pageId|页面|string|是|
|comId|组件id|string|是|

用法示例：
```javascript
window.weaverSDK.ready(function () {
  window.weaverSDK.compRender({
    container: containerDom,
    compName: 'designer.EBComView',
    params: { // 调用组件的参数
      pageId: "892725187490283521",
      comId: "e5f4aba44cc0414c9a2394c0c9e02d71",
    }
  });
});
```

#### EB 运行组件使用说明
`EBComView`组件是由`weapp-designer`项目中导出的组件运行视图，可使用它做单独EB组件的运行渲染。
可通过`weaverSDK.compRender`调用EB运行组件

- `compName` 组件名称：`designer.EBComView`
- `params` 参数说明如下：

| 名称 | 描述 | 类型 | 是否必填 |
| - | - | - | - |
|pageId|页面|string|是|
|comId|组件id|string|是|
|className|组件类名|string|否|
|client|客户端类型|ClientType|否|
|comServicePath|组件服务的路径|string|否|
|store|提供给组件的方法|CompViewStoreType|否|
|mounted|组件didmount时的回调|()=>void|否|
|customRender|组件自定义渲染|(viewNode: ReactElement) => ReactElement \| ReactNode|否|
|page|组件所属页面信息|Page|否|
|onMount|高阶组件(createView, createDesign)渲染完成后的回调|(dom: HTMLElement, context: any) => void|否|
|onContentMount|当内部的eb组件渲染完成后的回调，即高阶组件的Conent部分|(dom: HTMLElement, context: any) => void|否|


用法示例：
```js
window.weaverSDK.ready(function () {
  window.weaverSDK.compRender({
    container: containerDom,
    compName: 'designer.EBComView',
    params: { // 调用组件的参数
      pageId: "892725187490283521",
      comId: "e5f4aba44cc0414c9a2394c0c9e02d71",
    }
  });
});
```

##### 特别说明

页面id和组件id获取方式：

1.设计器提供入口获取（开发阶段，开发完成后可用此方式）：
![EB组件图1.png](/api/ecode/resource/view/${appRes}/1693793023-EB组件图1.png)

2.开发者工具获取：

现阶段页面 id（pageId）可从设计器 url 上获取，/sp/ebddesigner/pageId ,如下图 892725187490283521 即为pageId。
![EB组件图2.png](/api/ecode/resource/view/${appRes}/1693793045-EB组件图2.png)

现阶段组件 id（comId）可通过 dom 元素获取，设计器为每个组件的 dom 上绑定了各自业务 ID，设计、运行页面都可通过开发者工具（F12打开）中选中元素（Ctrl Shift C）来选中配置好的日程组件，选中的 dom 绑定的id即为组件业务 ID。如下图，获取到的日程组件业务 ID 即为 ca214705d5264ccda37a6bda95e54988 。
![EB组件图3.png](/api/ecode/resource/view/${appRes}/1693793058-EB组件图3.png)

### 2、流程组件使用说明

> 更详细的流程组件使用对接请参考文档：https://weapp.eteams.cn/ecode/playground/doc/share/view/989121717081915392 

#### 1、操作菜单组件
通过`weaverSDK.compRender`调用操作菜单组件
- `compName` 组件名称：`workflow.OpenComps`
- `params` 参数说明：

|名称|描述|类型|是否必填|
| - | - | - | - |
|wfCompName|组件名称|string|是|
|showButton|显示按钮|string|是|
|routeParam|路由参数|{}|是|
|topBtnNums|快捷按钮个数，默认显示2个，其他都在更多里|number|否|

#### 2、流转状态组件
通过`weaverSDK.compRender`调用流转状态组件
- compName 组件名称：workflow.OpenComps
- params 参数说明：

|名称|描述|类型|是否必填|
| - | - | - | - |
|wfCompName|组件名称|string|是|
|requestId|流程请求ID|string|是|

#### 3、流转意见组件
通过`weaverSDK.compRender`调用流流转意见组件
- compName 组件名称：workflow.OpenComps
- params 参数说明：

|名称|描述|类型|是否必填|
| - | - | - | - |
|wfCompName|组件名称|string|是|
|requestId|流程请求ID|string|是|
|hideTabs|隐藏页签|[]|否|

#### 4、示例
```js
const requestId = '895259957083422721';
window.weaverSDK.ready(function () {
  //操作菜单
  window.weaverSDK.compRender({
	  container: operateBtnRef.current,
	  compName: 'workflow.OpenComps',
	  params: { // 调用组件的参数
		  wfCompName: 'WffpOperBtns',
		  showButton:true,
		  routeParam:{requestId}
	  }
  });
  //流转意见
  window.weaverSDK.compRender({
	  container: commentRef.current,
	  compName: 'workflow.OpenComps',
	  params: { // 调用组件的参数
		  wfCompName: 'WffpSignList',
		  requestId:requestId,
		  hideTabs:['operationLog']
	  }
  });
  //流转状态
  window.weaverSDK.compRender({
	  container: statusRef.current,
	  compName: 'workflow.OpenComps',
	  params: { // 调用组件的参数
		  wfCompName: 'WffpStatus',
		  requestId
	  }
  });
});
```
#### 5、效果图
![流程示例图1.png](/api/ecode/resource/view/${appRes}/1693793085-流程示例图1.png)
![流程示例图2.png](/api/ecode/resource/view/${appRes}/1693793100-流程示例图2.png)

### 3、消息组件使用说明

#### 1、加载EM窗口
通过`weaverSDK.funcApply`调用加载EM窗口方法
- `funcName`  方法名称：`em.addEmWebBtn`
```js
weaverSDK.ready(() {
  window.weaverSDK.funcApply({
    funcName: 'em.addEmWebBtn'
  }).then((success) => {
  	// 成功回调
  }, (failed) => {
  	// 失败回调
  });
});
```

#### 2、打开/关闭EM主窗口
通过`weaverSDK.funcApply`调用打开/关闭EM主窗口方法
- `funcName`  方法名称：`em.setActiveWeb`
- `params` 参数说明：

|名称|描述|类型|是否必填|
| - | - | - | - |
|activeWeb|是否激活EM窗口|boolean|是|

```js
weaverSDK.ready(() {
  window.weaverSDK.funcApply({
    funcName: 'em.setActiveWeb',
    params: {
	activeWeb: true
    }
  }).then((success) => {
  	// 成功回调
  }, (failed) => {
  	// 失败回调
  });
});
```

#### 3、会话列表滚动到未读会话
通过`weaverSDK.funcApply`调用会话列表滚动到未读会话方法
- `funcName`  方法名称：`em.scrollUnreadChat`
```js
weaverSDK.ready(() {
  window.weaverSDK.funcApply({
    funcName: 'em.scrollUnreadChat'
  }).then((success) => {
  	// 成功回调
  }, (failed) => {
  	// 失败回调
  });
});
```

#### 4、效果图

##### 1、右下角工作消息和未读列表
![IM消息示例图1.png](/api/ecode/resource/view/${appRes}/1693793285-IM消息示例图1.png)

##### 2、主窗口
![IM消息示例图2.png](/api/ecode/resource/view/${appRes}/1693793306-IM消息示例图2.png)
