一、背景
领导说,客户最近提出一个需求,他要在企微里面,嵌入咱们的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去动态获取。
调用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?.getCurExternalContact
。 developer.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...

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