前端异步在CRM窗体中的使用方式

news/2025/1/16 10:47:34/文章来源:https://www.cnblogs.com/Hush-/p/18674527
## 一、异步解决了什么问题?🚀

1. **释放 UI 线程**  
   提升用户体验,避免页面卡顿。
![效果展示](./assets/测试onchange和保存事件.gif)

2. **优化代码结构**  
   减少冗余代码,逻辑更加清晰。
![效果展示](./assets/代码结构优化.png)

3. **缩短加载时间**  
   初始加载事件(如 `Onload` 和按钮 `Enable`)可同时执行,加快窗体加载速度。

 

## 二、使用的场景
按钮的显隐事件,onchange事件,onload事件,onsave事件,覆盖目前标准窗体缺少异步的场景。

 

## 三、使用代码示例

 

REST2.js

/*==================================================================
2024-11-22 Zhui.Yuan
--------------------------------------------------------------------REST v2.0===================================================================*/(async function () {const ORG_VERSION = "9.2";function REST2(url) {this.Heads = {'OData-MaxVersion': '4.0','OData-Version': '4.0','Accept': 'application/json','Prefer': 'odata.include-annotations=*','Content-Type': 'application/json; charset=utf-8'};this.ServerUrl = url || (window.Xrm ? Xrm.Page.context.getClientUrl() : "");this.ApiUrl = `/api/data/v${ORG_VERSION}/`;}REST2.prototype.createTimeoutPromise = function (timeout) {if(!timeout) timeout = 120000return new Promise((_, reject) =>setTimeout(() => reject(new Error('请求超时,请检查网络设置')), timeout));};REST2.prototype.Send = async function (url, method, data) {const fullUrl = this.ServerUrl + this.ApiUrl + url;var timeoutPromise = this.createTimeoutPromise();// 使用 Promise.race 来并行执行 fetch 和超时 Promisetry {const response = await Promise.race([fetch(fullUrl, {method,headers: this.Heads,body: data ? JSON.stringify(data) : null}),timeoutPromise  // 超时的 Promise
            ]);// 如果请求不成功,抛出错误if (!response.ok) {const errorData = await response.json();throw new Error(`Error: ${response.status} - ${errorData.error.message || response.statusText}`);}// 处理没有内容的响应return response.status !== 204 ? await response.json() : null;} catch (error) {// 捕获并处理超时和其他错误console.error(`Error in Send [${method}]:`, error.message);throw error;}};REST2.prototype.create = async function (entitySet, data) {return await this.Send(entitySet, 'POST', data);};REST2.prototype.update = async function (entitySet, id, data) {const requestURL = `${entitySet}(${id.replace(/{|}/g, '')})`;return await this.Send(requestURL, 'PATCH', data);};REST2.prototype.del = async function (entitySet, id) {const requestURL = `${entitySet}(${id.replace(/{|}/g, '')})`;return await this.Send(requestURL, 'DELETE');};REST2.prototype.get = async function (url) {return await this.Send(url, 'GET');};REST2.prototype.execFetchXml = async function (entitySet, fetchXml) {const url = `${this.ServerUrl}${this.ApiUrl}${entitySet}?fetchXml=${encodeURI(fetchXml)}`;var timeoutPromise = this.createTimeoutPromise();// 使用 Promise.race 来并行执行 fetch 和超时 Promisetry {const response = await Promise.race([fetch(url, {method: "GET",headers: this.Heads}),timeoutPromise  // 超时的 Promise
            ]);if (!response.ok) {const errorData = await response.json();throw new Error(`Error: ${response.status} - ${errorData.error.message || response.statusText}`);}return response.json();} catch (error) {console.error("Error in execFetchXml:", error.message);throw error;}};REST2.prototype.excuteAction = async function (actionName, object) {const fullUrl = this.ServerUrl + this.ApiUrl + actionName;var timeoutPromise = this.createTimeoutPromise();// 使用 Promise.race 来并行执行 fetch 和超时 Promisetry {const response = await Promise.race([fetch(fullUrl, {method: "POST",headers: this.Heads,body: JSON.stringify(object)}),timeoutPromise  // 超时的 Promise
            ]);if (!response.ok) {const errorData = await response.json();throw new Error(`Error: ${response.status} - ${errorData.error.message || response.statusText}`);}return response.json();} catch (error) {console.error("Error in excuteAction:", error.message);throw error;}};// 挂载到顶层window对象if (!top.REST2) {top.REST2 = REST2;}
})();

调用示例:

/*** REST2调用示例* @auth : YuanZhui* @date : 2024.1125*/var executionContext;
var Example = Example || {};
Example.saveFlag = false; // 全局保存标识var REST;
// 初始加载事件
Example.Onload = async (context) => {executionContext = context;// 引入REST2.jsif (!top.REST2) {var clientUrl = await Xrm.Page.context.getClientUrl();await loadScript(`${clientUrl}//WebResources/new_REST2.js`);
    }if (!REST) REST = await waitForREST2();var queryCurrency = "new_currencies?$select=new_currencyid,new_name&$filter=new_name eq 'CNY' and statecode eq 0";const value1 = await REST.get(queryCurrency);if (value1.length > 1) {//币种SetLookupValue("new_currency", "new_currency", value1[0].new_currencyid, value1[0].new_name, executionContext);}var queryBusinessunit = "businessunits?$select=name,new_unique_socialcode,new_address&$filter=name eq '日立电梯(中国)有限公司'";const value = await REST.get(queryBusinessunit);if (value.length > 0) {//申请人名称SetLookupValue("new_nameofapplicant", "businessunit", value[0].businessunitid, value[0].name, executionContext);//新建申请单时申请人相关信息默认赋值SetValue(executionContext, "new_applicantscc", value[0].new_unique_socialcode);SetValue(executionContext, "new_applicantaddress", value[0].new_address);}//合同如发生变化,带出相关值var contract = Xrm.Page.getAttribute("new_contract");contract.addOnChange(Example.ContractOnchange);};// 保存事件
Example.Onsave = async function (context) {if (!Xrm.Page.data.entity.getIsDirty()) { // 有更改才进入保存校验return;}// 为true时跳过验证,下一次保存恢复验证if (Example.saveFlag) {Example.saveFlag = falsereturn;}// 普通校验var new_nameofcontract = GetValue(executionContext, "new_nameofcontract");var new_contractsigndate = GetValue(executionContext, "new_contractsigndate");var isCheck = new_nameofcontract == null || new_contractsigndate == null;if (isCheck) {CrmDialog.alert("WARNING", "当前保函为履约保函,请填入相应合同名称和合同签订日期");context.getEventArgs().preventDefault();return}// 若受益人名称变更,则校验【受益人名称】和客户.【客户名称】是否相同,若不相同弹出提示。若确认无误,可点击确认按钮强制保存 var new_beneficiary = GetValue(executionContext, "new_beneficiary");// 受益人名称字段值有变化时 才进行校验var accountName = "";var account = GetValue(executionContext, "new_account");if (account != null) {var accountId =  account[0].id.replace("{", "").replace("}", "");//查询客户var queryAccount = "accounts(" + accountId + ")?$select=name";// 异步校验context.getEventArgs().preventDefault(); // 在使用await前先阻止保存,满足后再调用保存Xrm.Utility.showProgressIndicator("保存校验");var queryAccount = "accounts(" + accountId + ")?$select=name";var responseAccount = await REST.get(queryAccount); // 模拟多个请求等待时长var responseAccount1 = await REST.get(queryAccount);var responseAccount2 = await REST.get(queryAccount);//统一社会信用代码
            Xrm.Utility.closeProgressIndicator();if (responseAccount) {//统一社会信用代码accountName = responseAccount["name"];}}if (new_beneficiary != accountName) {context.getEventArgs().preventDefault();CrmDialog.confirm("提示", "受益人与客户不同,请检查并确认受益人统一社会信用代码与受益人地址!若确认无误,可点击确认按钮保存当前申请单",() => {Example.saveFlag = true; // 将全局变量设为true 下一次保存时直接跳过验证
                    Xrm.Page.data.save();}, () => {})}
};// onchange事件
Example.ContractOnchange = async ()=> {// 合同清空后,所有相关赋值的字段都需要清空SetValue(executionContext, "new_appointmentformat", null);SetValue(executionContext, "new_applydesc", null);var contract = GetValue(executionContext, new_contract);if (contract != null) {var contractId = contract[0].id.replace("{", "").replace("}", "");//查询合同var queryContract = "new_contracts(" + contractId + ")?$select=_new_keyaccount_r1_value,new_totalamount,_new_opportunity_r1_value,_new_account_r1_value,_new_businessunit_r1_value,new_contractdate,new_othercontnum";var responseText = await REST.get(queryContract);//合同签订日期if (IsOk(responseText.new_contractdate)) {SetValue(executionContext, "new_contractsigndate", new Date(responseText["new_contractdate"]));}//商机if (IsOk(responseText._new_opportunity_r1_value)) {SetLookupValue("new_opportunity", "opportunity", responseText._new_opportunity_r1_value, responseText["_new_opportunity_r1_value@OData.Community.Display.V1.FormattedValue"], executionContext);//商机相关字段var queryOpportunity = "opportunities(" + responseText._new_opportunity_r1_value + ")?$select=name,new_number,new_style";var responseOpportunityText = await REST.get(queryOpportunity, executionContext);//项目名称if (IsOk(responseOpportunityText.name)) {SetValue(executionContext, "new_projectname", responseOpportunityText.name);}}}
};// 按钮点击事件调用Action
Example.ExcuteActionBtn = async () => {var formContext = executionContext.getFormContext();var entityId = formContext.data.entity.getId().replace("{", "").replace("}", "");var parameters = {};parameters.entityId = entityId;Xrm.Utility.showProgressIndicator("同步中");var response = await REST.excuteAction("new_Action", parameters);Xrm.Utility.closeProgressIndicator();if (response.OutPutResult) {var data = JSON.parse(response.OutPutResult);if (data.issuccess == true) {CrmDialog.alert("INFO", "同步成功!");Xrm.Page.data.refresh();}else {CrmDialog.alert("WARNING", data.errMsg);}}
};// 按钮点击事件调用execFetchXml
Example.TestAsyncBtn = async () => {var contract = GetValue(executionContext, "new_contract");if (!contract) {alert("合同号为空")return;}Xrm.Utility.showProgressIndicator("开始查询");var fetchXml = `<fetch><entity name="new_contract"><attribute name="new_contractid" /><attribute name="new_other2" /><attribute name="new_other2name" /><attribute name="new_creditgrade" /><attribute name="new_revisedcontent" /><attribute name="new_revisedcontentname" /><attribute name="new_modify_quantity" /><filter type="and"><condition attribute="new_contractid" operator="eq" value="${contract[0].id}" /></filter></entity>
</fetch>`;var response = await REST.execFetchXml("new_contracts", fetchXml);console.log(response);Xrm.Utility.closeProgressIndicator();
}// 按钮异步显隐事件
Example.TestAsyncBtnEnable = async () => {var queryCurrency = "new_currencies?$select=new_currencyid,new_name&$filter=new_name eq 'CNY' and statecode eq 0";const value1 = await REST.get(queryCurrency);if (value1) {//币种return true;}return false;
}

## 四、实践中遇到的问题和解决方案

### 1. 异步方法加载顺序问题
**问题**:即使按顺序引用异步方法,脚本加载顺序不固定,导致后续脚本偶尔无法访问 `REST2` 对象。
**解决方案**
- 在需要的地方使用 `async/await` 主动加载文件:
    ```javascript
    // 引入 REST2.js
    if (!top.REST2) {
        varclientUrl = awaitXrm.Page.context.getClientUrl();
        awaitloadScript(`${clientUrl}//WebResources/new_REST2.js`);
    }
    ```
- **优点**`loadScript` 确保异步函数按顺序加载,避免窗体直接引用脚本时可能出现的 `REST2 is not defined` 错误。
---

### 2. `loadScript` 加载的脚本作用域问题
**问题**:使用 `loadScript` 加载的文件与当前脚本不在同一 `window` 层,无法访问 `REST2` 对象。
**解决方案**
1. 使用初始加载方法并声明 `async`
    ```javascript
    (asyncfunction () {
        // your code
    })();
    ```
2. 将类对象挂载到顶层 `window`
    ```javascript
    if (!top.REST2) {
        top.REST2 = REST2;
    }
    ```
### 3. 异步按钮事件与脚本加载的先后顺序问题
**问题**:即使 `Onload` 中已执行 `loadScript`,但按钮的 `Enable` 异步事件可能先于脚本生成。

**解决方案**
- 封装 `waitForREST2` 方法,确保 `REST2` 加载完成后再使用。

---

### 4. 在 `Onsave` 事件中使用 `async/await` 导致直接保存
**问题**:使用 `await` 时,`Onsave` 事件直接触发保存,可能导致数据错误。

**解决方案**
1. **阻止保存**:在使用 `await` 前先阻止保存,满足条件后再调用保存:
    ```javascript
    Example.saveFlag = true; // 全局变量设为 true
    ```
    ![保存示例]:

 

 

2. **声明保存标识**
    ![保存标识代码]

 


---
/**
 * REST2调用示例
 * @auth : YuanZhui
 * @date : 2024.1125
 */

var executionContext;
var Example = Example || {};
Example.saveFlag = false; // 全局保存标识


var REST;
// 初始加载事件
Example.Onload = async (context) => {
    executionContext = context;
    // 引入REST2.js
    if (!top.REST2) {
        var clientUrl = await Xrm.Page.context.getClientUrl();
        await loadScript(`${clientUrl}//WebResources/new_REST2.js`);
    }
    if (!REST) REST = await waitForREST2();
    var queryCurrency = "new_currencies?$select=new_currencyid,new_name&$filter=new_name eq 'CNY' and statecode eq 0";
    const value1 = await REST.get(queryCurrency);
    if (value1.length > 1) {
        //币种
        SetLookupValue("new_currency", "new_currency", value1[0].new_currencyid, value1[0].new_name, executionContext);
    }
    var queryBusinessunit = "businessunits?$select=name,new_unique_socialcode,new_address&$filter=name eq '日立电梯(中国)有限公司'";
    const value = await REST.get(queryBusinessunit);
    if (value.length > 0) {
        //申请人名称
        SetLookupValue("new_nameofapplicant", "businessunit", value[0].businessunitid, value[0].name, executionContext);
        //新建申请单时申请人相关信息默认赋值
        SetValue(executionContext, "new_applicantscc", value[0].new_unique_socialcode);
        SetValue(executionContext, "new_applicantaddress", value[0].new_address);
    }

    //合同如发生变化,带出相关值
    var contract = Xrm.Page.getAttribute("new_contract");
    contract.addOnChange(Example.ContractOnchange);

};

// 保存事件
Example.Onsave = async function (context) {
    if (!Xrm.Page.data.entity.getIsDirty()) { // 有更改才进入保存校验
        return;
    }
    // 为true时跳过验证,下一次保存恢复验证
    if (Example.saveFlag) {
        Example.saveFlag = false
        return;
    }

    // 普通校验
    var new_nameofcontract = GetValue(executionContext, "new_nameofcontract");
    var new_contractsigndate = GetValue(executionContext, "new_contractsigndate");
    var isCheck = new_nameofcontract == null || new_contractsigndate == null;
    if (isCheck) {
        CrmDialog.alert("WARNING", "当前保函为履约保函,请填入相应合同名称和合同签订日期");
        context.getEventArgs().preventDefault();
        return
    }

    // 若受益人名称变更,则校验【受益人名称】和客户.【客户名称】是否相同,若不相同弹出提示。若确认无误,可点击确认按钮强制保存
    var new_beneficiary = GetValue(executionContext, "new_beneficiary");
    // 受益人名称字段值有变化时 才进行校验
        var accountName = "";
        var account = GetValue(executionContext, "new_account");
        if (account != null) {
            var accountId =  account[0].id.replace("{", "").replace("}", "");
            //查询客户
            var queryAccount = "accounts(" + accountId + ")?$select=name";
            // 异步校验
            context.getEventArgs().preventDefault(); // 在使用await前先阻止保存,满足后再调用保存
            Xrm.Utility.showProgressIndicator("保存校验");
            var queryAccount = "accounts(" + accountId + ")?$select=name";
            var responseAccount = await REST.get(queryAccount); // 模拟多个请求等待时长
            var responseAccount1 = await REST.get(queryAccount);
            var responseAccount2 = await REST.get(queryAccount);
            //统一社会信用代码
            Xrm.Utility.closeProgressIndicator();
            if (responseAccount) {
                //统一社会信用代码
                accountName = responseAccount["name"];
            }
        }
        if (new_beneficiary != accountName) {
            context.getEventArgs().preventDefault();
            CrmDialog.confirm("提示", "受益人与客户不同,请检查并确认受益人统一社会信用代码与受益人地址!若确认无误,可点击确认按钮保存当前申请单",
                () => {
                    Example.saveFlag = true; // 将全局变量设为true 下一次保存时直接跳过验证
                    Xrm.Page.data.save();
                }, () => {
                })
        }
};

// onchange事件
Example.ContractOnchange = async ()=> {
    // 合同清空后,所有相关赋值的字段都需要清空
    SetValue(executionContext, "new_appointmentformat", null);
    SetValue(executionContext, "new_applydesc", null);

    var contract = GetValue(executionContext, new_contract);
    if (contract != null) {
        var contractId = contract[0].id.replace("{", "").replace("}", "");
        //查询合同
        var queryContract = "new_contracts(" + contractId + ")?$select=_new_keyaccount_r1_value,new_totalamount,_new_opportunity_r1_value,_new_account_r1_value,_new_businessunit_r1_value,new_contractdate,new_othercontnum";
        var responseText = await REST.get(queryContract);
        //合同签订日期
        if (IsOk(responseText.new_contractdate)) {
            SetValue(executionContext, "new_contractsigndate", new Date(responseText["new_contractdate"]));
        }
        //商机
        if (IsOk(responseText._new_opportunity_r1_value)) {
            SetLookupValue("new_opportunity", "opportunity", responseText._new_opportunity_r1_value, responseText["_new_opportunity_r1_value@OData.Community.Display.V1.FormattedValue"], executionContext);
            //商机相关字段
            var queryOpportunity = "opportunities(" + responseText._new_opportunity_r1_value + ")?$select=name,new_number,new_style";
            var responseOpportunityText = await REST.get(queryOpportunity, executionContext);
            //项目名称
            if (IsOk(responseOpportunityText.name)) {
                SetValue(executionContext, "new_projectname", responseOpportunityText.name);
            }
        }
    }
};

// 按钮点击事件调用Action
Example.ExcuteActionBtn = async () => {
    var formContext = executionContext.getFormContext();
    var entityId = formContext.data.entity.getId().replace("{", "").replace("}", "");
    var parameters = {};
    parameters.entityId = entityId;
    Xrm.Utility.showProgressIndicator("同步中");
    var response = await REST.excuteAction("new_Action", parameters);
    Xrm.Utility.closeProgressIndicator();
    if (response.OutPutResult) {
        var data = JSON.parse(response.OutPutResult);
        if (data.issuccess == true) {
            CrmDialog.alert("INFO", "同步成功!");
            Xrm.Page.data.refresh();
        }
        else {
            CrmDialog.alert("WARNING", data.errMsg);
        }
    }
};


// 按钮点击事件调用execFetchXml
Example.TestAsyncBtn = async () => {
    var contract = GetValue(executionContext, "new_contract");
    if (!contract) {
        alert("合同号为空")
        return;
    }
    Xrm.Utility.showProgressIndicator("开始查询");
    var fetchXml = `<fetch>
    <entity name="new_contract">
        <attribute name="new_contractid" />
        <attribute name="new_other2" />
        <attribute name="new_other2name" />
        <attribute name="new_creditgrade" />
        <attribute name="new_revisedcontent" />
        <attribute name="new_revisedcontentname" />
        <attribute name="new_modify_quantity" />
        <filter type="and">
            <condition attribute="new_contractid" operator="eq" value="${contract[0].id}" />
        </filter>
    </entity>
</fetch>`;

    var response = await REST.execFetchXml("new_contracts", fetchXml);
    console.log(response);
    Xrm.Utility.closeProgressIndicator();
}

// 按钮异步显隐事件
Example.TestAsyncBtnEnable = async () => {
    var queryCurrency = "new_currencies?$select=new_currencyid,new_name&$filter=new_name eq 'CNY' and statecode eq 0";
    const value1 = await REST.get(queryCurrency);
    if (value1) {
        //币种
        return true;
    }
    return false;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/870032.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IDEA如何快速定位到某一行某一列?

前言 大家好,我是小徐啊。我们在开发Java应用的时候,一般是用IDEA来开发的,毕竟这是一款功能强大的开发工具。我们可以使用IDEA做很多事情,今天小徐就来介绍下在使用IDEA开发的时候,如何快速定位到某个文件的某一行某一列。 如何快速定位到某一行某一列 首先,我们需要打开…

【附源码】JAVA花店管理后台系统源码+SpringBoot+VUE+前后端分离

学弟,学妹好,我是爱学习的学姐,今天带来一款优秀的项目:花店管理后台系统 。 本文介绍了系统功能与部署安装步骤,如果您有任何问题,也请联系学姐,偶现在是经验丰富的程序员! 一. 系统演示 系统测试截图系统视频演示https://githubs.xyz/show/341.mp4二. 系统概述【 系统…

初步使用动态web项目实现增删改查

我目前使用的工具是eclipse(2024-12),并且没用使用maven这类工具,数据库使用的是MySQL,服务器为tomcat10.1。 在编码并实现增删改查操作前,要进行一些准备工作。 首先在file里创建一个动态web项目(Dymamic Web Project),我们连接数据库要提前连接驱动与一个包,需要右…

Gitlab怎么升级

环境查看 系统环境# cat /etc/redhat-release Rocky Linux release 9.4 (Blue Onyx) [root@RockyEs01003081 ~]# uname -a Linux RockyEs01003081 5.14.0-427.35.1.el9_4.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Sep 12 18:24:53 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux软件环境…

基于Vector工具进行CAN协议错误帧的分析实践

引言CAN(Controller Area Network)协议是当前使用最普遍的车载通信协议之一,其优点不只体现在多主并行、最高达1Mbit/sec的传输速率(针对标准CAN)、基于优先级的仲裁机制以及广播发送的短帧结构,还体现在其错误检测机制上。通过总线数据以及总线波形来分析总线故障时,CA…

数字化办公时代的工时管理新模式

在当今数字化浪潮的推动下,传统的工时管理方式已经难以满足高效办公的需求。在线文档协作工具作为数字化办公的关键环节,正在深刻改变工时管理的模式与效率。本文将探讨在线协作工具在工时管理中的独特优势及其对现代办公模式的深远影响。一、在线文档协作的数字化优势 1. 信…

[车联网/以太网] SOME/IP 协议

概述: SOME/IP 协议车载以太网协议栈总共可划分为5层:物理层 数据链路层 网络层 传输层 应用层其中本文所要描述的SOME/IP就是一种应用层协议。SOME/IP协议内容按照AUTOSAR中的描述,我们可以更进一步的拆分为3类子协议:应用层的SOME/IP标准协议 SOME/IP-SD协议 TP层的SOME/IP…

[附源码]图书管理系统+SpringBoot+Vue前后端分离

今天带来一款优秀的项目:图书借阅管理系统源码 。 系统采用的流行的前后端分离结构,内含功能包括 "系统权限角色",“登录,注册”,“图书管理”,“借阅管理”,“图书类别管理”,“系统账号管理”。 如果您有任何问题,也请联系小编,小编是经验丰富的程序员!…

深入解析 Spring AI 系列:解析函数调用

我们之前讨论并实践过通过常规的函数调用来实现 AI Agent 的设计和实现。但是,有一个关键点我之前并没有详细讲解。今天我们就来讨论一下,如何让大模型只决定是否调用某个函数,但是Spring AI 不会在内部处理函数调用,而是将其代理到客户端。然后,客户端负责处理函数调用,…

【YashanDB知识库】YMP校验从yashandb同步到oracle的数据时,字段timestamp(0)出现不一致

本文内容来自YashanDB官网,原文内容请见 https://www.yashandb.com/newsinfo/7901520.html?templateId=1718516 问题现象 在YMP校验过程中,从yashandb同步到oracle的数据时,字段timestamp(0)出现不一致问题的风险及影响 YMP校验出现数据内容不一致 问题影响的版本 yashandb…

提升团队协作效率:必备的多人协作软件清单

企业团队协作离不开多人协作软件的支持。这些软件各有特色,功能各异,企业可以根据自身的需求和实际情况进行评估和选择。选择一款适合自己的协作软件,不仅能够提升团队效率,还能让团队协作更加顺畅、高效。在快节奏的现代办公环境中,企业团队协作变得尤为重要。为了提升团…

东方通更新HTTPS证书,开启SSL协议

东方通更新HTTPS证书,开启SSL协议1、上传jks证书2、进入tongweb管理页面,打开WEB容器配置->HTTP通道管理。点击tong-http-listener3、修改HTTP为HTTPS,注意端口由80变为443。证书类型改成JKS,证书路径就是上传的路径,还有证书密码