vue自建h5应用,接入企业微信JDK(WECOM-JSSDK),实现跳转添加好友功能

一、项目场景:

1、使用vue开发了一套h5页面的项目

2、这个h5链接是在企业微信里某个地方打开的

3、打开页面的时候有一个好友列表,点击好友列表某一条复制手机号跳转到企业微信添加好友页面

二、实现的效果图

博客只允许上传gif图,所以我只能手机进行录屏,然后再拿gif录一次video给大家展示(如果他不动了,你就刷新一下页面)

三、实现方案

1、通过 npm 引入(企业微信 WECOM-JSSDK)

javascript 复制代码
//第一种方式  npm引入
npm install @wecom/jssdk   (我用的这种)
//也有第二种方法(别问为啥不用这个,问就是因为我不会)
<script src="https://wwcdn.weixin.qq.com/node/open/js/wecom-jssdk-1.3.1.js"></script>

2、页面中使用

javascript 复制代码
import * as ww from '@wecom/jssdk'

3、页面初始化注册企微的jdk(强度来了,非非非常烧脑,需要细看细看细看)

在看这条之前你需要先明白几点:

1、想要调用企微的东西,通俗说需要先把你自己的项目让企微进行授权,简单思路就是:

appid+url----> 换code ---->拿code---->换access_token

2、(解释第一点)你拿自己的企微id(appid)以及页面地址,去获取企微的code,拿到code后去换取access_token,拿到token后就能进行你自己页面的接口等其他操作了

javascript 复制代码
created() {
    let appid = "wx7*************";    这是你的企微id
    this.url = location.href.split("#")[0];     获取当前页面的地址,注意你的路由模式,如果路由中携带#,那么就需要截取#之前的 (有问题参考文章后参考资料第三个链接内的第六点)
    this.initCode(appid);   这个方法就是获取企微授权那一套
},
methods: {
	//初始化页面调用方法,如果没有code,就去企微进行授权(企微授权后会返回到这个页面,并且吧code赋到url后边,截取拿地址栏的参数就ok),如果有了code就走自己页面的正常逻辑
	 async initCode(appid) {
      let code = this.getUrlParam("code");
      if (!code) {
      	//这个地方其实就是死套路,你只需要关注给企微的链接传入appid,url就可以
        const urls = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${this.url}&response_type=code&scope=snsapi_base&state=highSeas#wechat_redirect`;
        window.location.href = urls;
      } else {
      //这块就是企微回调回code后回到这个页面,然后拿code获取access_token就可以进行后续的接口及企微jdk的初始化了
        let postData = {
          code: code,
        };
        qrLogin(postData).then((res) => {
       	 //把token存起来
          sessionStorage.setItem("token", res.access_token);
          this.wxConfig(appid);
        });
      }
    },
     // 获取url参数  (封装的一个获取ulr地址参数的方法)  
    getUrlParam(name) {
      const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
      const result = window.location.search.substr(1).match(reg);
      return result ? decodeURIComponent(result[2]) : null;
    },
    最最最最关键点来了 ---- 关键 ------  ---- 关键 ------  ---- 关键 ------  ---- 关键 ------  ---- 关键 ------
    //初始化注册企微jdk
    wxConfig(appid) {
      let dataInfo = {
        url: this.url,
        appType: 1,
      };
      //调用企微的方法,用url去拿签名等信息  
      //我们请求的是这个接口  "ticket/getAgentTicket"
      //https://developer.work.weixin.qq.com/document/24364#%E8%8E%B7%E5%8F%96%E5%BA%94%E7%94%A8%20jsapi_ticket
      //企微的链接,生成签名算法,需要后端配合你一块查看,如果这个签名生成不正确就会报错,比如后续图一,
      getAgentTicket(dataInfo).then((res) => {
        this.dataVal = res.data.data;
        let timestamp = this.dataVal.timestamp;
        let nonceStr = this.dataVal.nonceStr;
        let signature = this.dataVal.signature;
        ww.register({
          corpId: appid, // 必填,当前用户企业所属企业ID
          agentId: "1000XXX",      //找后端宝贝要
          jsApiList: ["navigateToAddCustomer", "scanQRCode", "openUserProfile"], // 必填,需要使用的JSAPI列表  
          getConfigSignature, // 必填,根据url生成企业签名的回调函数
          getAgentConfigSignature,    // 必填,根据url生成应用签名的回调函数
          onConfigSuccess: (result) => {
            // alert(result,"success");
          },
          onConfigFail: (result) => {
            alert(JSON.stringify(result), "failed");
          },
        });
        function getConfigSignature(url) {
          // 根据 url 生成企业签名
          // 生成方法参考 https://developer.work.weixin.qq.com/document/path/90539
          return { timestamp, nonceStr, signature };
        }
        async function getAgentConfigSignature(url) {
          // 根据 url 生成应用签名,生成方法同上,但需要使用应用的 jsapi_ticket
          return { timestamp, nonceStr, signature };
        }
      });
      //截止到这块初始化企微jdk就结束了,接下来就可以直接做你想要的操作了,比如跳转到企微的添加好友,比如调用企微的扫一扫等
    },
    //接下来就用按钮绑定一个事件,调用你想用的方法就行了
    setGoto(text) {
        ww.navigateToAddCustomer({
          success(result) {
            // alert(JSON.stringify(result), "1111");
            // 成功回调,result.errMsg 固定格式为"方法名:ok"
          },
          fail(result) {
            alert(JSON.stringify(result), "32222");
            // 失败回调,通过 result.errMsg 查看失败详情
          },
          complete(result) {
            // alert(JSON.stringify(result), "333333");
            // 完成回调,无论调用成功还是失败,都会回调该方法
          },   
      });
    },
}

四、注意点

4.1、无效签名

这个代码40093问题是很常见的,当时一直在翻企微的社区文章找错误解决办法,结果改了一圈没有一个类似的,然后又重新返回去按照文章步骤去一步一步排查,终于发现了问题点在哪

解决点1:我用的是npm引入的方法,并且是h5自建应用,所以应该用这种方法去初始化,而我用的是上边"企业身份与权限",没细看自己的需求直接文章都不翻去用,导致卡了很久

解决点2:后端直接扔个我一个获取签名的接口getAppTicket 说只有这么一个接口绝对没问题,然后又去拿签名验证工具重复的看也没问题,死活非说前端报错,这可一顿好找啊,后来我就一直纠结那个签名算法生成的文章,逼着后端宝贝一块看真的没问题嘛?最终发现了。又给了我一个getAgentTicket接口,说换这个试试,然后一举成功。

getAgentTicket 获取应用的jsapi_ticket

getAppTicket 获取企业的jsapi_ticket

4.2 没有权限

1、调到这块我信誓旦旦觉得没问题了,所以就去手机端尝试,结果又给我报这个错没有权限。

2、我以为是哪块又配置的不对,初始化的时候在jsApiList也配置了自己想要的api了啊。然后我就去调用的扫一扫的功能ww.scanQRCode({needResult: true,scanType: 'qrCode'}) 放到手机里直接使用,可以出来扫一扫界面,那就找到了初始化这块肯定是成功了,那就是这个跳转好友这块有问题,然后又去翻文章。


1、后台配置一下添加好友权限即可

至此全部功能已经实现,总结来说按照官方文档步步排查走踩坑少,自己想做什么都不明确,光靠一个报错去搜索解答会浪费很多时间。多看文档,少瞎胡搜报错,少走弯路

还有一个点,不管是初始化jdk还是调用后续的api方法,企微都有Success,跟Fail的回调,可以快速的帮助你找到报错原因。

4.3这个常见报错文档很有用,多看

参考资料

主要的参考资料链接,其实都是企微的官方文档,主要注意的点就差不多这几个
https://developer.work.weixin.qq.com/document/path/98132 (第一个肯定是企业微信的开发文档了,项目怎么接入企微的jdk)
https://developer.work.weixin.qq.com/document/24364#获取应用 jsapi_ticket (这个还是属于企微的文档,但也是最关键的一步,怎么生成签名------需要后端同事一块查看)
https://developer.work.weixin.qq.com/document/path/90542 (依旧企微的文档,常见错误问题排查,比如最主要的签名错误)

全部代码(如果你功能跟我一样或者类似呢,盲猜替换一下appid跟agentId就可以直接使用)

javascript 复制代码
<template>
  <van-tabs sticky v-model="active" @click-tab="onClickTab">
    <van-tab name="first" :title="'待添加(' + total + ')'">
      <van-list
        v-if="active == 'first'"
        v-model:loading="loading"
        :finished="finished"
        finished-text="没有更多了"
        :immediate-check="false"
        @load="getData"
        :offset="20"
        class="contentList"
      >
        <van-cell>
          <template v-for="(unit, key) in list" :key="key">
            <div class="content">
              <div>{{ unit.phone }}</div>
              <div>
                {{ unit.customerName }}
              </div>
              <div style="display: flex">
                <van-button
                  plain
                  size="small"
                  hairline
                  type="primary"
                  @click="copyFn($event, unit.phone)"
                  >复制并添加</van-button
                >
              </div>
            </div>
          </template>
        </van-cell>
      </van-list>
    </van-tab>
    <van-tab name="second" title="待通过"></van-tab>
    <van-tab name="three" title="已添加"></van-tab>
  </van-tabs>
</template>
   
  <script>
import {
  getTypeList,
  setState,
  getAgentTicket,
  wcRedirect,
  qrLogin,
} from "../api/highSeas";
import { showNotify, closeNotify } from "vant";
import ClipboardJS from "clipboard";
import * as ww from "@wecom/jssdk";
export default {
  data() {
    return {
      active: "first",
      list: [],
      loading: false,
      finished: false,
      total: 0,
      query: {
        pageSize: 20,
        pageNum: 1,
        addState: 0, // 0:待添加;1:已添加;3:待通过
      },
    };
  },
  created() {
    let appid = "wxXXXXXXXXXXXXXXXX";
    this.url = location.href.split("#")[0];
    this.initCode(appid);
  },
  methods: {
    onClickTab(info) {
      this.active = info.name;
      if (info.name == "first") {
        this.query.pageNum = 1;
        this.list = [];
        this.loading = false;
        this.finished = false;
        getTypeList(this.query).then((res) => {
          this.total = res.data.total;
          this.list.push(...res.data.rows);
        });
        this.query.pageNum = 2;
      }
    },
    async initCode(appid) {
      let code = this.getUrlParam("code");
      if (!code) {
        const urls = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${this.url}&response_type=code&scope=snsapi_base&state=highSeas#wechat_redirect`;
        window.location.href = urls;
      } else {
        let postData = {
          code: code,
        };
        qrLogin(postData).then((res) => {
          this.initQueryVal = true;
          sessionStorage.setItem("token", res.access_token);
          this.init();
          this.wxConfig(appid);
        });
      }
    },
    // 获取url参数
    getUrlParam(name) {
      const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
      const result = window.location.search.substr(1).match(reg);
      return result ? decodeURIComponent(result[2]) : null;
    },
    wxConfig(appid) {
      let dataInfo = {
        url: this.url,
        appType: 1,
      };
      getAgentTicket(dataInfo).then((res) => {
        this.dataVal = res.data.data;
        let timestamp = this.dataVal.timestamp;
        let nonceStr = this.dataVal.nonceStr;
        let signature = this.dataVal.signature;
        ww.register({
          corpId: appid, // 必填,当前用户企业所属企业ID
          agentId: "10XXXXXX",
          jsApiList: ["navigateToAddCustomer", "scanQRCode", "openUserProfile"], // 必填,需要使用的JSAPI列表
          getConfigSignature, // 必填,根据url生成企业签名的回调函数
          getAgentConfigSignature,
          onConfigSuccess: (result) => {
            // alert(result,"success");
          },
          onConfigFail: (result) => {
            alert(JSON.stringify(result), "failed");
          },
        });
        function getConfigSignature(url) {
          // 根据 url 生成企业签名
          // alert(this.dataVal.nonceStr, "this.dataVal.nonceStr");
          // alert(
          //   this.dataVal.signature,
          //   "this.dataVal.signaturesignaturesignaturesignaturesignature"
          // );
          // 生成方法参考 https://developer.work.weixin.qq.com/document/path/90539
          return { timestamp, nonceStr, signature };
        }
        async function getAgentConfigSignature(url) {
          // 根据 url 生成应用签名,生成方法同上,但需要使用应用的 jsapi_ticket
          return { timestamp, nonceStr, signature };
        }
      });
    },
    init() {
      getTypeList(this.query).then((res) => {
        this.total = res.data.total;
        this.list.push(...res.data.rows);
      });
      this.query.pageNum = 2;
    },
    getData() {
      getTypeList(this.query).then((res) => {
        let dataInfo = res.data.rows;
        this.loading = false;
        this.query.pageNum += 1;
        this.total = res.data.total;
        this.list.push(...dataInfo);
        if (this.list.length >= Number(this.total)) {
          this.finished = true;
        } else {
          this.finished = false;
        }
      });
    },
    setGoto(text) {
      setState({ phone: text }).then((res) => {
        ww.navigateToAddCustomer({
          success(result) {
            // alert(JSON.stringify(result), "1111");
            // 成功回调,result.errMsg 固定格式为"方法名:ok"
          },
          fail(result) {
            alert(JSON.stringify(result), "32222");
            // 失败回调,通过 result.errMsg 查看失败详情
          },
          complete(result) {
            // alert(JSON.stringify(result), "333333");
            // 完成回调,无论调用成功还是失败,都会回调该方法
          },
        });
        this.list = [];
        this.query.pageNum = 1;
        this.finished = false;
        this.init();
      });
    },
    copyFn(e, text) {
      const clipboard = new ClipboardJS(e.target, { text: () => text });
      clipboard.on("success", (e) => {
        this.setGoto(text);
        showNotify({ type: "success", message: "复制成功" });
        // 释放内存
        clipboard.off("error");
        clipboard.off("success");
        clipboard.destroy();
      });
      clipboard.on("error", (e) => {
        // 不支持复制
        // this.$toast({
        //   type: "fail",
        //   message: "该浏览器不支持自动复制",
        //   icon: "none",
        // });
        showNotify({ type: "warning", message: "该浏览器不支持自动复制" });
        // 释放内存
        clipboard.off("error");
        clipboard.off("success");
        clipboard.destroy();
      });
      clipboard.onClick(e);
    },
  },
};
</script>
<style scoped lang='scss'>
.content {
  font-size: 15px;
  font-weight: 500;
  display: flex;
  justify-content: space-between;
  align-items: center;
  line-height: 60px;
}
.contentList {
  // overflow-y: scroll;
  // height: calc(100vh - 60px); //
}

::v-deep .van-button--info {
  border: none;
}
</style>
相关推荐
大模型码小白29 分钟前
【Python零基础教程】继承、多态与魔法函数:面向对象编程三大核心特性详解
java·大数据·开发语言·人工智能·python·ai编程
腾渊信息科技公司1 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
爱笑的源码基地2 小时前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
慧一居士4 小时前
Element Plus 按需引入的配置使用说明和完整示例
前端·vue.js
莫逸风5 小时前
【AgentScope 2.0】 0. 学习指南
java·llm·agent·agentscope
z123456789865 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
yaoxin5211236 小时前
470. Java 反射 - Member 接口与 AccessFlag
java·开发语言·python
做个文艺程序员6 小时前
Linux第24篇:Java应用监控体系搭建:Prometheus+Grafana可视化运维
java·grafana·prometheus
小钻风33667 小时前
Spring Boot 文件上传详解:深入理解 MultipartFile 的使用与原理
java·开发语言
其美杰布-富贵-李7 小时前
Spring Boot 工程开发全流程说明
java·spring boot·后端