JS-SDK开发企微侧边栏

一、背景

领导说,客户最近提出一个需求,他要在企微里面,嵌入咱们的h5页面,方便查看当前用户购买的产品信息。之前咱们没接触过这块,你回头调研一下。

企微侧边栏是一种常见的应用开发,他可以根据客户的诉求,开发任意类型的页面,包括小程序等等,灵活展示,前提是运行在企微环境中。开发前建议详细阅读企微的开发文档developer.work.weixin.qq.com/document/pa... 因为我这里的需求是h5所以采用的是js-sdk。

二、企微后台配置

登录企微后台work.weixin.qq.com/ 扫码登录,注意登录时会选择企业,选了哪个企业就是在哪个企业下配置应用。

1、创建应用

(1)、依次单击【应用管理】--【应用管理】--【应用】--在"自建"区【创建应用】

按要求上传 LOGO、填写表单后,单击【创建应用】。

(2)、打开应用,复制应用名称、AgentId 和 Secret,发给后端同学,他们会拿这个进行授权登录。

(3)、复制企业 ID

依次单击【我的企业】--【企业信息】,滑到页面底部,复制企业 ID,发给后端同学,也是授权登录用。

2、应用配置

(1)、配置"应用主页",点击【设置】

根据需要选择网页或者是小程序。

我这里选择"网页"开发,网址地址就是你开发的h5应用的地址。

这里有个细节就是链接里面可以把agentid和corpid拼在链接上,因为后续应用当中需要用到这个参数,如果不放在链接里的话,就需要调用js-sdk去动态获取。

eg:xxxx.com/xxx/xxx/ind...

调用js-sdk方法内部涉及多个异步调用,本身就特别慢,所以能不调就不调吧。参数啥的完全可以放到链接里面。

配置完后单击【确定】

(2)、配置"配置到聊天工具栏",单击【配置】

单击【配置页面】

输入"页面名称",单击【确定】。

(3)、配置"网页授权及 JS-SDK",单击【设置可信域名】

在"可信域名"输入你应用当中需要使用的所有域名,包括接口请求的域名。

单击【申请校验域名】,按提示下载文本文件。

这是个.txt类型的文件。

如果你的开发的项目就是你部署到服务器的项目,你可以把它放在你项目的根目录下。

因为我这里比较特殊,开发的项目跟部署的项目不是一个,所以得放在部署项目的根目录下。

等待上传完成,单击【确定】

(4)、配置"企业微信授权登录",单击【设置】

在 Web 网页"授权回调域"输入回调地址,(就是你调后端登录接口的域名),单点【保存】

(5)、配置"企业可信 IP",单击【配置】

在文本框输入ip地址,开发人员的ip,需要使用这个应用人员的ip(多个ip中间英文分号分隔),单击【确定】

3、设置外部联系人应用

(1)、设置可访问外部联系人的应用,依次单击【客户与上下游】--【客户关系】--【客户】--【API】--【修改】

在"设置可调用接口的应用"列表勾选刚才创建的应用,单击【确定】

到这里,创建跟配置应用部分就大功告成了!

三、实战操作

1、项目配置

(1)根组件

首先创建个h5工程(这里不赘述了),或者在已有工程上开发都可以。因为页面可能有多个。我这里采用的是动态组件的方式进行开发的,你也可以使用路由,比较随意了。

js 复制代码
<template>
  <div id="app">
    <keep-alive v-if="currentView">
      <component :is="currentView" :userInfo="userInfo" />
    </keep-alive>
  </div>
</template>
js 复制代码
export default {
  name: "App",
  components: {
    // 首页
    LhIndex: () => import("./pages/index"),
    // 跟踪
    LhTrack: () => import("./pages/track"),
    // 详情
    LhDetail: () => import("./pages/detail"),
    // 介绍
    LhIntro: () => import("./pages/stockIntro"),
  },
  created() {
    // 初始化函数
    this.init();
  },
  methods: {
    // 初始化
    init() {
      switch (this.$getWorkType) {
        case "index":
          this.currentView = "LhIndex";
          break;
        case "track":
          this.currentView = "LhTrack";
          break;
        case "detail":
          this.currentView = "LhDetail";
          break;
        case "intro":
          this.currentView = "LhIntro";
          break;
      }
      console.log("设置currentView为:", this.currentView);
    },
};

$getWorkType是通过window.loation.href拿到页面的路径,并获取页面的后缀名,根据后缀名去匹配页面,我把它挂在了全局。

其实这么做有一个问题,就是需要运维同学去帮你修改nginx配置。

当访问任何页面时,都把资源指向index.html。

如果你有两个页面分别是a.html、b.html。当访问这俩页面时,都指向index.html的资源(不然的话,访问页面会报资源找不到的错),这样就可以使用动态组件这种模式开发多页应用了。

(2)入口文件

其次在你的index.html文件中引入企微sdk。

js 复制代码
<!-- 企业微信sdk -->
    <script src="https://wwcdn.weixin.qq.com/node/open/js/wecom-jssdk-2.0.2.js"></script>

2、登录

(1)获取参数

上面提到过,corpid和agentid等信息字段是可以放到链接里的,我这里通过一个挂在全局的方法直接拿到这两个参数,作为接口请求入参。

js 复制代码
  const corpid = this.$getUrlParam("corpid") || "";
  const agentid = this.$getUrlParam("agentid") || "";
  this.corpid = corpid;
  this.agentid = agentid;

(2)授权方式

根据需求,只是在企微内嵌了h5页面,所以不需要登录界面,采用的是静默登录方式,既打开页面默认登录。这里需要你的后端配合,根据企微提供OAuth2登录流程developer.work.weixin.qq.com/document/pa...

在未登录时需要你在请求你后端同学包装好的链接地址,地址后面附上登录成功之后需要重定向的地址gourl,也就是你h5的首页地址。登录成功之后,直接回到首页。

js 复制代码
// 如果用户信息不存在,则跳转到授权登录页面
console.warn("用户信息不存在,跳转授权页面");
window.location.href = `https://${this.$isTest ? "test" : ""}xxx.com.cn/api/v1/oauth/we-com/login?corpid=${corpid}&agentid=${agentid}&gourl=${encodeURIComponent(`https://${this.$isTest ? "test" : ""}xxx.com.cn/xx/xx/index.html?corpid=${corpid}&agentid=${agentid}`)}`;

用户登录过了则有登录信息,需要去调用企微的获取签名接口。

js 复制代码
// 设置用户信息
  this.userInfo = res?.data?.info;
  console.log(this.userInfo, "this.res");
  try {
    // 获取签名
    this.getCompanySign();
  } catch (error) {
    console.warn("获取企业签名失败:", error);
  }

(3)获取签名

签名分为企业签名和应用签名,根据需要获取即可。我这里只需要获取应用签名。

this.getCompanySign()里面首先做一些兼容判断,以防报错直接挂掉。

js 复制代码
  let ww = window?.ww;
  
  // 检查是否在企微环境中
  if (!ww) {
    console.warn("企业微信 SDK 未加载");
    return;
  }

  // 检查必要参数
  if (!this.corpid || !this.agentid) {
    console.warn("缺少必要参数: corpid 或 agentid");
    return;
  }

获取到签名,timestamp、nonceStr、signature都是固定返回字段,直接取就好了。

js 复制代码
async function getConfigSignature(type) {
    try {
      const response = await that.$getData("getSignApi", {
        param: {
          ...param,
          type: type,
        },
      });

      console.log("签名接口返回:", response);

      if (response && response?.data) {
        console.log("签名成功:", response.data);
        return {
          timestamp: response?.data?.timestamp,
          nonceStr: response?.data?.nonceStr,
          signature: response?.data?.signature,
        };
      } else {
        throw new Error("签名接口返回数据格式错误");
      }
    } catch (error) {
      console.warn("签名失败:", error);
      throw error;
    }
  }

拿到之后作为返回值,直接调企微的注册wx.register。 developer.work.weixin.qq.com/document/pa...

签名的返回值作为getAgentConfigSignature的value。

js 复制代码
ww?.register({
  corpId: that?.corpid,
  agentId: that?.agentid,
  jsApiList: ["getCurExternalContact"],
  getAgentConfigSignature: () => getConfigSignature("agent"),
});
js 复制代码
try {
    console.log("=== 开始注册企业微信 ===");

    // 检查 ww.register 是否存在
    if (typeof ww?.register !== "function") {
      console.warn("ww.register 方法不存在");
      return;
    }

    ww?.register({
      corpId: that?.corpid,
      agentId: that?.agentid,
      jsApiList: ["getCurExternalContact"],
      getAgentConfigSignature: () => getConfigSignature("agent"),
    });

    console.log("企业微信应用注册成功,JSAPI 已准备就绪。");

    // 使用更安全的方式等待注册完成
    setTimeout(() => {
      console.log("延迟调用 getCurExternalContact...");
      // 调用获取外部联系人方法
      that?.getCurExternalContactFunc();
    }, 1000); // 增加延迟时间确保注册完成
  } catch (error) {
    console.warn("企业微信注册失败:", error);
  }

(4)获取外部联系人ID

需求是要在页面中获取到外部联系人购买的产品信息,所以必须首先获取到外部联系人的ID给到你亲爱的后端。

注册好了之后获取外部联系人ID。 ww?.getCurExternalContactdeveloper.work.weixin.qq.com/document/pa...

js 复制代码
   getCurExternalContactFunc() {
      console.log("=== 开始调用 getCurExternalContact ===");

      // 检查 ww 对象
      if (!ww) {
        console.warn("企微 SDK 未加载");
        return;
      }

      // 检查方法是否存在
      if (typeof ww?.getCurExternalContact !== "function") {
        console.warn("getCurExternalContact 方法不存在");
        console.log("可用的 ww 方法:", Object.keys(ww));
        return;
      }
      console.log("准备调用 ww.getCurExternalContact...");

      try {
        // 调用企业微信SDK的getCurExternalContact接口
        ww?.getCurExternalContact({
          success: (res) => {
            console.log("获取外部联系人成功:", res);
            if (
              res &&
              res.errCode === 0 &&
              res.errMsg === "getCurExternalContact:ok"
            ) {
              // 保存到本地存储
              try {
                localStorage.setItem("externalUserId", res.userId);
                console.log("externalUserId已保存到本地存储:", res.userId);
              } catch (error) {
                console.warn("保存externalUserId到本地存储失败:", error);
              }
            } else {
              console.warn("返回数据格式不正确:", res);
            }
          },
          fail: (err) => {
            console.warn("获取外部联系人失败:", err);
          },
        });
      } catch (error) {
        console.warn("调用 getCurExternalContact 时发生错误:", error);
      }
    },

获取完之后,我给它存在了本地,方便在多个页面中获取。

(5)页面中调用

这就涉及到了很多异步方法,wx.register、wx.getCurExternalContact、外加你调后端的接口等等。所以干脆直接来了个轮询调用。

在首页mounted中调用startPollingExternalUserId

js 复制代码
startPollingExternalUserId() {
  console.log("开始轮询检查externalUserId");
  // 记录开始轮询的时间,用于确保获取到的是新的externalUserId
  this.pollingStartTime = Date.now();

  this.externalUserIdPollingTimer = setInterval(() => {
    const externalUserId = localStorage.getItem("externalUserId");
    console.log("轮询检查externalUserId:", externalUserId);
    if (externalUserId) {
      console.log("轮询发现externalUserId,停止轮询并开始加载数据");
      clearInterval(this.externalUserIdPollingTimer);
      this.externalUserIdPollingTimer = null;
      // 获取接口数据
      this.pullData();
    }
  }, 500); // 每500ms检查一次

  // 设置最大轮询时间,避免无限等待
  setTimeout(() => {
    if (this.externalUserIdPollingTimer) {
      console.warn("轮询超时,停止轮询");
      clearInterval(this.externalUserIdPollingTimer);
      this.externalUserIdPollingTimer = null;
      this.loading = false;
    }
  }, 20000); // 20秒超时
},

请求页面数据之前,优先获取外部联系人IDexternalUserId。拿到之后调用 this.pullData()

这里加了个最大轮询时间20秒,以防陷入死循环。

3、开发调试

本地开发调试参考友情链接:juejin.cn/post/753277...

如果你想抓包,可以参考企微文档: developer.work.weixin.qq.com/document/pa...

开发过程中可能会遇到各种问题,包括调企微sdk时的各种状态码错误信息。不过别担心,企微很贴心的提供了踩坑记录: developer.work.weixin.qq.com/document/pa...

四、最后

实际开发的一个小案例,希望能够帮到你!

相关推荐
万少1 小时前
可可图片编辑 HarmonyOS(6)水印效果
前端·harmonyos
FlowGram2 小时前
低代码设计态变量挑战与设计 — 前端变量引擎介绍
前端·低代码
大西瓜瓜瓜2 小时前
PS2020,将所有图片不剪切,调整为800×800像素的文档尺寸。
前端
sjin2 小时前
React源码 - 大名鼎鼎的Fiber
前端
子兮曰2 小时前
🚀从单体到Monorepo:四川省xxx协会官网架构重生记
前端·next.js·turbopack
bosscheng2 小时前
0到1理解web音视频从采集到传输到播放系列之《Jessibuca系列篇音视频解封装》
javascript·音视频开发
白水清风2 小时前
CI/CD学习记录(基于GitLab)
前端·自动化运维·前端工程化
齐杰拉2 小时前
源码精读:拆解 ChatGPT 打字机效果背后的数据流水线
前端·chatgpt
文心快码BaiduComate2 小时前
“一人即团队”——一句话驱动智能体团队
前端·后端·程序员