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...

四、最后

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

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax