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

news2024/11/16 18:00:42

一、项目场景:

1、使用vue开发了一套h5页面的项目
2、这个h5链接是在企业微信里某个地方打开的
3、打开页面的时候有一个好友列表,点击好友列表某一条复制手机号跳转到企业微信添加好友页面

二、实现的效果图

在这里插入图片描述

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

三、实现方案

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

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

2、页面中使用

import * as ww from '@wecom/jssdk'

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

在看这条之前你需要先明白几点:
1、想要调用企微的东西,通俗说需要先把你自己的项目让企微进行授权,简单思路就是:
appid+url----> 换code ---->拿code---->换access_token
2、(解释第一点)你拿自己的企微id(appid)以及页面地址,去获取企微的code,拿到code后去换取access_token,拿到token后就能进行你自己页面的接口等其他操作了

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#%E8%8E%B7%E5%8F%96%E5%BA%94%E7%94%A8%20jsapi_ticket (这个还是属于企微的文档,但也是最关键的一步,怎么生成签名——需要后端同事一块查看)
https://developer.work.weixin.qq.com/document/path/90542 (依旧企微的文档,常见错误问题排查,比如最主要的签名错误)

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQL注入-下篇

HTTP注入 一、Referer注入 概述 当你访问一个网站的时候&#xff0c;你的浏览器需要告诉服务器你是从哪个地方访问服务器的。如直接在浏览器器的URL栏输入网址访问网站是没有referer的&#xff0c;需要在一个打开的网站中&#xff0c;点击链接跳转到另一个页面。 Less-19 判…

预算有限?如何挑选经济适用的ERP系统?

中小企业在运营过程中&#xff0c;经常面临着一个共同的挑战——如何在有限的预算内挑选到一款既符合业务需求又经济适用的ERP系统。然而&#xff0c;市场上ERP系统种类繁多&#xff0c;价格差异大&#xff0c;功能复杂&#xff0c;使得许多企业在选择时感到迷茫和困惑。 如果…

【BEV】BEVFormer总结

本文分享BEV感知方案中&#xff0c;具有代表性的方法&#xff1a;BEVFormer。 它基于Deformable Attention&#xff0c;实现了一种融合多视角相机空间特征和时序特征的端到端框架&#xff0c;适用于多种自动驾驶感知任务。 主要由3个关键模块组成&#xff1a; BEV Queries Q&am…

带你了解甘肃独特的调料-苦豆粉

在众多的调味料中&#xff0c;苦豆粉是一种相对小众但却极具特色的存在。今天&#xff0c;就让我们一起深入探究甘肃特产苦豆粉的奇妙世界。苦豆粉&#xff0c;又被称为胡巴豆、大巴豆等&#xff0c;它主要源自于一种豆科植物。很多人初次接触苦豆粉时&#xff0c;可能会被它独…

13.2 Go 接口的动态性

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

RK3588 Android12音频驱动分析全网最全

最近没有搞音频相关的了&#xff0c;在搞BMS, 把之前的经验总结一下。 一、先看一下Android 12音频总架构 从这张图可以看到音频数据流一共经过了3个用户空间层的进程&#xff0c;然后才流到kernel驱动层。Android版本越高&#xff0c;通用性越高&#xff0c;耦合性越低&#…

【Portswigger 学院】CORS

教程和靶场来源于 Burpsuite 的官网 Portswigger&#xff1a;Cross-origin resource sharing (CORS) - PortSwigger 跨域资源共享&#xff08;Cross-origin resource sharing&#xff0c;CORS&#xff09;是一种浏览器机制&#xff0c;允许浏览器访问不同源的资源。同源策略的作…

【Python】已解决Python错误:ImportError: cannot import name get_column_letter的报错解决办法

【Python】已解决Python错误&#xff1a;ImportError: cannot import name get_column_letter的报错解决办法 &#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家…

Chat-TTS chat-tts-ui 实机部署上手测试!Ubuntu服务器实机 2070Super*2 8GB部署全流程

项目介绍 开源的项目&#xff0c;感谢各位大佬的贡献&#xff01; 官方介绍&#xff1a;一个简单的本地网页界面&#xff0c;使用ChatTTS将文字合成为语音&#xff0c;同时支持对外提供API接口。A simple native web interface that uses ChatTTS to synthesize text into spe…

没有名为 keras.preprocessing 的模块

估计是因为版本原因 我安装的是 3.3.3版本 >>> import keras >>> print(keras.__version__) 3.3.3 keras.preprocessing.image 将 keras.preprocessing.image 改为 from keras_preprocessing.image 之后报image_type啥的错误&#xff0c;后面查找之后…

C++ 84 之 文件读写

#include <iostream> #include <cstring> #include <string> using namespace std; #include <fstream> // 文件流的头文件int main() {// 写入: 文件内容// (文件位置&#xff0c; 如果这个不存在&#xff0c; 就新建一个)// 写法1&#xff1a; of…

JAVA实现利用phantomjs对URL页面(网页)进行转图片保存

一、前期准备 1、下载phantomjs工具 地址&#xff1a;https://phantomjs.org/download.html 解压到指定文件夹&#xff0c;后续代码要调用该工具&#xff0c;记住路径 2、准备好模板NetToPicMoban.js 用于给phantomjs提供需要执行的js&#xff0c;具体放在那看自己的需求&…

运算放大器(运放)缓冲器(跟随器)电路

运算放大器(Operational Amplifier) 运算放大器(Operational Amplifier)是一种差分放大器&#xff0c;具有高输入电阻、低输出电阻、高开放增益&#xff08;开环增益&#xff09;&#xff0c;并具有可放大输入引脚与-输入引脚间的电压差的功能。 设计目标 输入输入输出输出频…

“Photoshop AI插件:StartAI的全面使用攻略

随着人工智能技术的飞速发展&#xff0c;Photoshop作为设计师们不可或缺的工具&#xff0c;也在不断地融入AI技术&#xff0c;以提升设计效率和效果。在2024年&#xff0c;PSAI插件StartAI因其强大的功能和易用性&#xff0c;成为了Photoshop用户的得力帮手。下面来给大家详细介…

银行数仓项目实战(五)--搭建数仓和数据标准化

文章目录 搭建数仓数据采集 标准层 搭建数仓 数据采集 业务系统源 -》ODS贴源层&#xff0c;添加标签&#xff0c;添加系统来源&#xff0c;添加时间戳 问甲方要建表语句&#xff0c;自己添加etldt字段和来源字段&#xff0c;通过之前文章教的Kettle把数据抽到自己数据库中。…

远程桌面另一台服务器连接不上,局域网IP如何访问另一台服务器

在IT运维工作中&#xff0c;远程桌面连接是日常工作中不可或缺的一部分。然而&#xff0c;当尝试远程桌面连接至另一台服务器时&#xff0c;如果连接不上&#xff0c;可能会引发一系列问题&#xff0c;影响到工作效率和信息安全。特别是在局域网环境中&#xff0c;确保能够正确…

MySQL学习——创建MySQL Workbench中的Connections

在MySQL Workbench中&#xff0c;Connections&#xff08;连接&#xff09;是用户与MySQL数据库进行交互的桥梁。 本文将添加一个新连接&#xff0c;该连接可以是初始连接&#xff0c;也可以是附加连接。在开始之前&#xff0c;必须安装、启动MySQL服务器的实例&#xff0c;并…

今年618各云厂商的香港服务器优惠活动汇总

又到了一年618年中钜惠活动时间&#xff0c;2024年各大云服务器厂商都有哪些活动呢&#xff1f;有哪些活动包括香港服务器呢&#xff1f;带着这些问题&#xff0c;小编给大家一一讲解各大知名厂商的618活动有哪些值得关注的地方&#xff0c;如果对你有帮助&#xff0c;欢迎点赞…

SK投屏助手:电脑控制手机,游戏与App轻松畅玩

SK投屏助手让您将手机画面完美投射至电脑屏幕&#xff0c;而且不止于此&#xff01;现在&#xff0c;您可以利用电脑反向控制手机&#xff0c;轻松操作各种游戏和应用程序。无论是在家中放松还是在工作中提高效率&#xff0c;SK投屏助手都能成为您的得力助手。立即体验无缝连接…

51单片机STC89C52RC——3.1 数码管静态展示

目的 让数码管在指定位置显示指定数字 一&#xff0c;STC单片机模块 二&#xff0c;数码管 2.1 数码管位置 2.2 生活中用到的数目管 红绿灯 LED数码管在生活中随处可见&#xff0c;洗衣机、电饭煲、热水器、微波炉、冰箱、这些最基本的家用电器上基本都用到了这种7段LED数…