您的当前位置:首页>全部文章>文章详情

企业微信最新的jssdk使用说明 WECOM-JSSDK Demo

发表于:2025-08-16 09:29:33浏览:31次TAG: #ThinkPHP #PHP #企业微信

企业微信的WECOM-JSSDK与JS-SDK的异同,官方是这样描述的:

旧版本(原JS-SDK)仍支持使用,相较旧版本WECOM-JSSDK提供了Typescript类型支持、npm引入等新能力。但企业微信官方推荐使用的是最新的WECOM-JSSDK,WECOM-JSSDK能做什么?简而言之,WECOM-JSSDK为网页应用提供了调用原生能力的通道,网页应用通过 WECOM-JSSDK 可以调起拍照、选择图片、录音、获取地理位置信息等手机系统能力。同时可以使用企业微信分享、扫一扫等企业微信微信特有能力,为企业微信用户提供更优质的网页应用体验。

使用WECOM-JSSDK最关键的一步就是鉴权,其他接口调用就是正常的api调用即可,在调用 JSAPI 前,需要先通过 ww.register 注册当前页面的身份信息。身份信息分为两种:

企业身份与权限
应用(自建应用/第三方应用等)身份与权限。之所以有两种,是有些api使用企业身份即可,有些api需要使用应用身份。总体应用身份能做的事情更多一些。

注册身份信息时,需要提供对应的签名函数,签名是根据当前页面的 URL 和 jsapi_ticket 等信息生成的。
jsapi_ticket敏感信息,需要在服务端完成签名操作。

JS-SDK 签名算法企业微信官方文档:https://developer.work.weixin.qq.com/document/path/96909

具体根据企业微信官方文档开发即可,这里贴出基于thinkphp的完整代码:
创建QyWechatService.php文件

<?php
namespace app\qiye;
use think\facade\Cache;
use think\facade\Log;

class QyWechatService
{
    // 获取access_token
    public static function getAccessToken()
    {
        $cacheKey = 'qywx_access_token';
        $token = Cache::get($cacheKey);
        if (!$token) {
            $config = config('workchat');
            $url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={$config['corpid']}&corpsecret={$config['corpsecret']}";
            $response = json_decode(file_get_contents($url), true);

            if (empty($response['access_token'])) {
                Log::error('获取access_token失败: ' . json_encode($response));
                throw new \Exception("获取access_token失败");
            }

            $token = $response['access_token'];
            // 缓存7000秒(官方有效期7200秒)
            Cache::set($cacheKey, $token, 7000);
        }
        return $token;
    }

    // 获取企业jsapi_ticket
    public static function getJsapiTicket()
    {
        $cacheKey = 'qywx_jsapi_ticket';
        $ticket = Cache::get($cacheKey);
        if (!$ticket) {
            $accessToken = self::getAccessToken();
            $url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token={$accessToken}";
            $response = json_decode(file_get_contents($url), true);

            if (empty($response['ticket'])) {
                Log::error('获取jsapi_ticket失败: ' . json_encode($response));
                throw new \Exception("获取jsapi_ticket失败");
            }

            $ticket = $response['ticket'];
            // 缓存7000秒
            Cache::set($cacheKey, $ticket, 7000);
        }
        return $ticket;
    }

    // 生成企业JS-SDK签名
    public static function getSignature($url)
    {
        $ticket = self::getJsapiTicket();
        $nonceStr = self::createNonceStr(16); // 随机字符串
        $timestamp = time();

        // 按字典序拼接参数
        $string = "jsapi_ticket={$ticket}&noncestr={$nonceStr}×tamp={$timestamp}&url={$url}";
        $signature = sha1($string);

        return [
            'corpid'     => config('workchat.corpid'), // 企业微信的CorpID
            'timestamp' => $timestamp,
            'nonceStr'  => $nonceStr,
            'signature' => $signature,
            'url'       => $url // 可选,前端需校验URL一致性
        ];
    }

    // 获取应用 jsapi_ticket
    public static function getAgentJsapiTicket()
    {
        $cacheKey = 'qywx_jsapi_ticket_a';
        $ticket = Cache::get($cacheKey);
        if (!$ticket) {
            $accessToken = self::getAccessToken();
            $url = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token={$accessToken}&type=agent_config";
            $response = json_decode(file_get_contents($url), true);

            if (empty($response['ticket'])) {
                Log::error('获取jsapi_ticket失败: ' . json_encode($response));
                throw new \Exception("获取jsapi_ticket失败");
            }

            $ticket = $response['ticket'];
            // 缓存7000秒
            Cache::set($cacheKey, $ticket, 7000);
        }
        return $ticket;
    }

    // 生成应用JS-SDK签名
    public static function getAgentConfigSignature($url)
    {
        $ticket = self::getAgentJsapiTicket();
        $nonceStr = self::createNonceStr(16); // 随机字符串
        $timestamp = time();

        // 按字典序拼接参数
        $string = "jsapi_ticket={$ticket}&noncestr={$nonceStr}×tamp={$timestamp}&url={$url}";
        $signature = sha1($string);

        return [
            'corpid'     => config('workchat.corpid'), // 企业微信的CorpID
            'timestamp' => $timestamp,
            'nonceStr'  => $nonceStr,
            'signature' => $signature,
            'url'       => $url // 可选,前端需校验URL一致性
        ];
    }

    // 生成随机字符串
    private static function createNonceStr($length = 16)
    {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }
}

创建Api.php文件

<?php
declare (strict_types = 1);
namespace app\qiye\controller;
use app\qiye\BaseController;
use app\qiye\QyWechatService;

class Api extends BaseController
{
    /**
     * 构造函数
     */
    public function __construct()
    {
        $this->workchat = get_config('workchat');
    }
    //jssdk
    public function jssdk()
    {
        try {
            $param = get_params();
            $currentUrl = $param['url']; // 前端传递当前页面URL
            if (empty($currentUrl)) {
                return json(['code' => 400, 'msg' => 'URL参数缺失']);
            }            
            $config = QyWechatService::getSignature($currentUrl);
            return json(['code' => 0, 'data' => $config]);

        } catch (\Exception $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()]);
        }
    }

    //jssdk
    public function jssdkww()
    {
        try {
            $param = get_params();
            $currentUrl = $param['url']; // 前端传递当前页面URL
            if (empty($currentUrl)) {
                return json(['code' => 400, 'msg' => 'URL参数缺失']);
            }            
            $config = QyWechatService::getAgentConfigSignature($currentUrl);
            return json(['code' => 0, 'data' => $config]);

        } catch (\Exception $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()]);
        }
    }
}

前端文件:

<html>
<head>
    <meta charset="utf-8">
    <title>企业微信JS-SDK Demo</title>
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0">
    <style type="text/css">
        html {
            -ms-text-size-adjust: 100%;
            -webkit-text-size-adjust: 100%;
            -webkit-user-select: none;
            user-select: none;
        }

        body {
            line-height: 1.6;
            font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
            background-color: #f1f0f6;
        }

        * {
            margin: 0;
            padding: 0;
        }

        button {
            font-family: inherit;
            font-size: 100%;
            margin: 0;
            *font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
        }

        ul,
        ol {
            padding-left: 0;
            list-style-type: none;
        }

        a {
            text-decoration: none;
        }

        .label_box {
            background-color: #ffffff;
        }

        .label_item {
            padding-left: 15px;
        }

        .label_inner {
            padding-top: 10px;
            padding-bottom: 10px;
            min-height: 24px;
            position: relative;
        }

        .label_inner:before {
            content: " ";
            position: absolute;
            left: 0;
            top: 0;
            width: 200%;
            height: 1px;
            border-top: 1px solid #ededed;
            -webkit-transform-origin: 0 0;
            transform-origin: 0 0;
            -webkit-transform: scale(0.5);
            transform: scale(0.5);
            top: auto;
            bottom: -2px;
        }

        .lbox_close {
            position: relative;
        }

        .lbox_close:before {
            content: " ";
            position: absolute;
            left: 0;
            top: 0;
            width: 200%;
            height: 1px;
            border-top: 1px solid #ededed;
            -webkit-transform-origin: 0 0;
            transform-origin: 0 0;
            -webkit-transform: scale(0.5);
            transform: scale(0.5);
        }

        .lbox_close:after {
            content: " ";
            position: absolute;
            left: 0;
            top: 0;
            width: 200%;
            height: 1px;
            border-top: 1px solid #ededed;
            -webkit-transform-origin: 0 0;
            transform-origin: 0 0;
            -webkit-transform: scale(0.5);
            transform: scale(0.5);
            top: auto;
            bottom: -2px;
        }

        .lbox_close .label_item:last-child .label_inner:before {
            display: none;
        }

        .btn {
            display: block;
            margin-left: auto;
            margin-right: auto;
            padding-left: 14px;
            padding-right: 14px;
            font-size: 18px;
            text-align: center;
            text-decoration: none;
            overflow: visible;
            height: 42px;
            border-radius: 5px;
            -moz-border-radius: 5px;
            -webkit-border-radius: 5px;
            box-sizing: border-box;
            -moz-box-sizing: border-box;
            -webkit-box-sizing: border-box;
            color: #ffffff;
            line-height: 42px;
            -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
        }

        .btn.btn_inline {
            display: inline-block;
        }

        .btn_primary {
            background-color: #437DBA;
        }

        .btn_primary:not(.btn_disabled):visited {
            color: #ffffff;
        }

        .btn_primary:not(.btn_disabled):active {
            color: rgba(255, 255, 255, 0.9);
            background-color: #3b78b9;
        }

        button.btn {
            width: 100%;
            border: 0;
            outline: 0;
            -webkit-appearance: none;
        }

        button.btn:focus {
            outline: 0;
        }

        .wxapi_container {
            font-size: 16px;
        }

        h1 {
            font-size: 14px;
            font-weight: 400;
            line-height: 2em;
            padding-left: 15px;
            color: #8d8c92;
        }

        .desc {
            font-size: 14px;
            font-weight: 400;
            line-height: 2em;
            color: #8d8c92;
        }

        .wxapi_index_item a {
            display: block;
            color: #3e3e3e;
            -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
        }

        .wxapi_form {
            background-color: #ffffff;
            padding: 0 15px;
            margin-top: 30px;
            padding-bottom: 15px;
        }

        h3 {
            padding-top: 16px;
            margin-top: 25px;
            font-size: 16px;
            font-weight: 400;
            color: #3e3e3e;
            position: relative;
        }

        h3:first-child {
            padding-top: 15px;
        }

        h3:before {
            content: " ";
            position: absolute;
            left: 0;
            top: 0;
            width: 200%;
            height: 1px;
            border-top: 1px solid #ededed;
            -webkit-transform-origin: 0 0;
            transform-origin: 0 0;
            -webkit-transform: scale(0.5);
            transform: scale(0.5);
        }

        .btn {
            margin-bottom: 15px;
        }
    </style>
    </style>
</head>

<body ontouchstart="">
<div class="wxapi_container">
        <div class="wxapi_index_container">
            <ul class="label_box lbox_close wxapi_index_list">
                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-basic">基础接口</a> </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-enterprise">通讯录接口</a> </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-chat">会话接口</a> </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-share">分享接口</a> </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-webview">界面接口</a> </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-sys-webview">系统界面接口</a>
                </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-image">图像接口</a> </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-voice">录音接口</a> </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-open">开放接口</a> </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-file">文件接口</a> </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-clipboard">剪贴板接口</a> </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-network">网络接口</a> </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-location">地理位置接口</a> </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-client">客户联系接口</a> </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-schedule">日程接口</a>
                </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-meeting">会议接口</a>
                </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-living">直播接口</a>
                </li>

                <li class="label_item wxapi_index_item"> <a class="label_inner" href="#menu-approval">审批接口</a>
                </li>
            </ul>
        </div>

        <div class="lbox_close wxapi_form">
            <h3 id="menu-basic">基础接口</h3>
            <span class="desc">判断当前客户端是否支持指定JS接口</span>
            <button class="btn btn_primary" data-type="checkJsApi"> checkJsApi </button>
            <span class="desc">判断用户是从哪个入口打开页面</span>
            <button class="btn btn_primary" data-type="getContext"> getContext </button>

            <h3 id="menu-enterprise">通讯录接口</h3>
            <span class="desc">选择通讯录成员</span>
            <button class="btn btn_primary" data-type="selectEnterpriseContact">selectEnterpriseContact</button>
            <span class="desc">调起个人信息页面</span>
            <button class="btn btn_primary" data-type="openUserProfile">openUserProfile</button>

            <h3 id="menu-chat">会话接口</h3>
            <span class="desc">打开会话</span>
            <button class="btn btn_primary" data-type="openEnterpriseChat">openEnterpriseChat</button>

            <h3 id="menu-share">分享接口</h3>
            <span class="desc">监听「转发」按钮点击</span>
            <button class="btn btn_primary" data-type="onMenuShareAppMessage">onMenuShareAppMessage</button>
            <span class="desc">监听「微信」按钮点击</span>
            <button class="btn btn_primary" data-type="onMenuShareWechat">onMenuShareWechat</button>
            <span class="desc">监听「朋友圈」按钮点击</span>
            <button class="btn btn_primary" data-type="onMenuShareTimeline">onMenuShareTimeline</button>
            <span class="desc">自定义转发到会话</span>
            <button class="btn btn_primary" data-type="shareAppMessage">shareAppMessage</button>
            <span class="desc">自定义转发到微信</span>
            <button class="btn btn_primary" data-type="shareWechatMessage">shareWechatMessage</button>

            <h3 id="menu-webview">界面接口</h3>
            <span class="desc">监听页面返回</span>
            <button class="btn btn_primary" data-type="onHistoryBack">onHistoryBack</button>
            <span class="desc">隐藏右上角菜单</span>
            <button class="btn btn_primary" data-type="hideOptionMenu">hideOptionMenu</button>
            <span class="desc">显示右上角菜单</span>
            <button class="btn btn_primary" data-type="showOptionMenu">showOptionMenu</button>
            <span class="desc">关闭当前窗口</span>
            <button class="btn btn_primary" data-type="closeWindow">closeWindow</button>
            <span class="desc">批量隐藏功能按钮</span>
            <button class="btn btn_primary" data-type="hideMenuItems">hideMenuItems</button>
            <span class="desc">批量显示功能按钮</span>
            <button class="btn btn_primary" data-type="showMenuItems">showMenuItems</button>
            <span class="desc">隐藏非基础按钮</span>
            <button class="btn btn_primary" data-type="hideAllNonBaseMenuItem">hideAllNonBaseMenuItem</button>
            <span class="desc">显示非基础按钮</span>
            <button class="btn btn_primary" data-type="showAllNonBaseMenuItem">showAllNonBaseMenuItem</button>
            <span class="desc">打开默认浏览器(仅支持PC端)</span>
            <button class="btn btn_primary" data-type="openDefaultBrowser">openDefaultBrowser</button>

            <h3 id="menu-sys-webview">系统界面接口</h3>
            <span class="desc">调起扫一扫</span>
            <button class="btn btn_primary" data-type="scanQRCode">scanQRCode</button>
            <span class="desc">跳转认证界面</span>
            <button class="btn btn_primary" data-type="enterpriseVerify">enterpriseVerify</button>
            <span class="desc">调起应用管理界面</span>
            <button class="btn btn_primary" data-type="openAppManage">openAppManage</button>

            <h3 id="menu-image">图像接口</h3>
            <span class="desc">选择图片</span>
            <button class="btn btn_primary" data-type="chooseImage"> chooseImage </button>
            <div class="image_container" id="imageContainer"></div>
            <span class="desc">预览图片</span>
            <button class="btn btn_primary" data-type="previewImage"> previewImage </button>
            <span class="desc">上传图片</span>
            <button class="btn btn_primary" data-type="uploadImage"> uploadImage </button>
            <span class="desc">下载图片</span>
            <button class="btn btn_primary" data-type="downloadImage"> downloadImage </button>
            <div class="image_container" id="imageContainerForDownload"></div>

            <h3 id="menu-voice">录音接口</h3>
            <div class="desc" id="voiceToast"></div>
            <span class="desc">开始录音</span>
            <button class="btn btn_primary" data-type="startRecord"> startRecord </button>
            <span class="desc">停止录音</span>
            <button class="btn btn_primary" data-type="stopRecord"> stopRecord </button>
            <span class="desc">播放语音</span>
            <button class="btn btn_primary" data-type="playVoice"> playVoice </button>
            <span class="desc">暂停播放</span>
            <button class="btn btn_primary" data-type="pauseVoice"> pauseVoice </button>
            <span class="desc">停止播放</span>
            <button class="btn btn_primary" data-type="stopVoice"> stopVoice </button>
            <span class="desc">上传语音</span>
            <button class="btn btn_primary" data-type="uploadVoice"> uploadVoice </button>
            <span class="desc">下载语音</span>
            <button class="btn btn_primary" data-type="downloadVoice"> downloadVoice </button>
            <span class="desc">语音转文字</span>
            <button class="btn btn_primary" data-type="translateVoice"> translateVoice </button>

            <h3 id="menu-open">开放接口</h3>
            <span class="desc">创建企业微信登录面板</span>
            <button class="btn btn_primary" data-type="createWWLoginPanel"> createWWLoginPanel </button>
            <div id="ww_login"></div>

            <h3 id="menu-file">文件接口</h3>
            <span class="desc">预览文件</span>
            <button class="btn btn_primary" data-type="previewFile"> previewFile </button>

            <h3 id="menu-clipboard">剪贴板接口</h3>
            <span class="desc">设置剪贴板内容</span>
            <button class="btn btn_primary" data-type="setClipboardData"> setClipboardData </button>
            <span class="desc">获取剪贴板内容</span>
            <button class="btn btn_primary" data-type="getClipboardData"> getClipboardData </button>

            <h3 id="menu-network">网络接口</h3>
            <span class="desc">获取网络状态</span>
            <button class="btn btn_primary" data-type="getNetworkType"> getNetworkType </button>
            <span class="desc">监听网络状态</span>
            <button class="btn btn_primary" data-type="onNetworkStatusChange"> onNetworkStatusChange </button>

            <h3 id="menu-location">地理位置接口</h3>
            <div class="desc" id="locationToast"></div>
            <span class="desc">打开内置地图</span>
            <button class="btn btn_primary" data-type="openLocation"> openLocation </button>
            <span class="desc">获取地理位置</span>
            <button class="btn btn_primary" data-type="getLocation"> getLocation </button>
            <span class="desc">打开持续定位</span>
            <button class="btn btn_primary" data-type="startAutoLBS"> startAutoLBS </button>
            <span class="desc">停止持续定位</span>
            <button class="btn btn_primary" data-type="stopAutoLBS"> stopAutoLBS </button>
            <span class="desc">监听地理位置</span>
            <button class="btn btn_primary" data-type="onLocationChange"> onLocationChange </button>

            <h3 id="menu-client">客户联系接口</h3>
            <span class="desc">调起外部联系人列表</span>
            <button class="btn btn_primary" data-type="selectExternalContact"> selectExternalContact </button>
            <span class="desc">群发消息给客户</span>
            <button class="btn btn_primary" data-type="shareToExternalContact"> shareToExternalContact </button>
            <span class="desc">群发消息到客户群</span>
            <button class="btn btn_primary" data-type="shareToExternalChat"> shareToExternalChat </button>
            <span class="desc">调起添加客户界面</span>
            <button class="btn btn_primary" data-type="navigateToAddCustomer"> navigateToAddCustomer </button>
            <span class="desc">发表内容到客户朋友圈</span>
            <button class="btn btn_primary" data-type="shareToExternalMoments"> shareToExternalMoments </button>
            <span class="desc">设置朋友圈封面与签名</span>
            <button class="btn btn_primary" data-type="updateMomentsSetting"> updateMomentsSetting </button>

            <h3 id="menu-schedule">日程接口</h3>
            <span class="desc">查看日程闲忙</span>
            <button class="btn btn_primary" data-type="checkSchedule"> checkSchedule </button>

            <h3 id="menu-meeting">会议接口</h3>
            <span class="desc">创建会议并调起会议室页面</span>
            <button class="btn btn_primary" data-type="startMeeting"> startMeeting </button>

            <h3 id="menu-living">直播接口</h3>
            <span class="desc">创建直播并调起直播页面</span>
            <button class="btn btn_primary" data-type="startLiving"> startLiving </button>
            <span class="desc">调起直播间回放</span>
            <button class="btn btn_primary" data-type="replayLiving"> replayLiving </button>
            <span class="desc">调起直播回放下载页面</span>
            <button class="btn btn_primary" data-type="downloadLivingReplay"> downloadLivingReplay </button>

            <h3 id="menu-approval">审批接口</h3>
            <span class="desc">应用发起审批</span>
            <button class="btn btn_primary" data-type="thirdPartyOpenPage"> thirdPartyOpenPage </button>
        </div>
    </div>

// alert(ww.SDK_VERSION)
const messageDiv = document.getElementById('message');
const chooseImageButton = document.getElementById('chooseImage');

// basic info
corpId = 'your_corp_id';
agentId = 1000005; // change to your agent id
jsApiList = ['checkJsApi', 'get、、Context',
    'selectEnterpriseContact', 'openUserProfile',
    'openEnterpriseChat',
    'onMenuShareAppMessage', 'onMenuShareWechat', 'onMenuShareTimeline', 'shareAppMessage', 'shareWechatMessage',
    'onHistoryBack', 'hideOptionMenu', 'showOptionMenu', 'closeWindow', 'hideMenuItems', 'showMenuItems', 'hideAllNonBaseMenuItem', 'showAllNonBaseMenuItem', 'openDefaultBrowser', 'onUserCaptureScreen',
    'scanQRCode', 'enterpriseVerify', 'openAppManage',
    'chooseImage', 'previewImage', 'uploadImage', 'downloadImage', 'getLocalImgData',
    'startRecord', 'stopRecord', 'playVoice', 'pauseVoice', 'stopVoice', 'uploadVoice', 'downloadVoice', 'translateVoice',
    'createWWLoginPanel',
    'previewFile',
    'setClipboardData', 'getClipboardData',
    'getNetworkType', 'onNetworkStatusChange',
    'openLocation', 'getLocation', 'startAutoLBS', 'stopAutoLBS', 'onLocationChange',
    'selectExternalContact', 'shareToExternalContact', 'shareToExternalChat', 'navigateToAddCustomer', 'shareToExternalMoments', 'updateMomentsSetting',
    'checkSchedule',
    'startMeeting',
    'startLiving', 'replayLiving', 'downloadLivingReplay',
    'thirdPartyOpenPage',];

// // 企业身份与权限
// ww.register({
//     corpId: corpId,       // 必填,当前用户企业所属企业ID
//     jsApiList: jsApiList, // 必填,需要使用的JSAPI列表
//     getConfigSignature    // 必填,根据url生成企业签名的回调函数
// })

// 应用身份与权限
ww.register({
    corpId: corpId,         // 必填,当前用户企业所属企业ID
    agentId: agentId,       // 必填,当前应用的AgentID
    jsApiList: jsApiList,   // 必填,需要使用的JSAPI列表
    getConfigSignature,     // 必填,根据url生成企业签名的回调函数
    getAgentConfigSignature // 必填,根据url生成应用签名的回调函数
})

async function getConfigSignature(url) {
    // 根据 url 生成企业签名
    // 生成方法参考 https://developer.work.weixin.qq.com/document/14924
    const response = await fetch('/amyu/get_corp_jssdk_config', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ url })
    })
    if (!response.ok) {
        throw new Error('Network response was not ok ' + response.statusText);
    }
    const data = await response.json();
    const { timestamp, nonceStr, signature } = data;
    return { timestamp, nonceStr, signature }
}

async function getAgentConfigSignature(url) {
    // 根据 url 生成应用签名,生成方法同上,但需要使用应用的 jsapi_ticket
    const response = await fetch('/amyu/get_app_jssdk_config', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ url })
    })
    if (!response.ok) {
        throw new Error('Network response was not ok ' + response.statusText);
    }
    const data = await response.json();
    const { timestamp, nonceStr, signature } = data;
    return { timestamp, nonceStr, signature }
}

images = { localId: [], serverId: [] };
voice = { localId: "", serverId: "" };
userids = [];
livingIds = [];
var shareConfig = {
    title: "互联网之子",
    desc: "在长大的过程中,我才慢慢发现,我身边的所有事,别人跟我说的所有事,那些所谓本来如此,注定如此的事,它们其实没有非得如此,事情是可以改变的。更重要的是,有些事既然错了,那就该做出改变。",
    link: "http://movie.douban.com/subject/25785114/",
    imgUrl: "http://demo.open.weixin.qq.com/jssdk/images/p2166127561.jpg",
    success: function (e) {
        alert("已分享");
    },
    cancel: function (e) {
        alert("已取消");
    },
    fail: function (e) {
        alert(JSON.stringify(e));
    },
};

// 调用 register 后可以立刻调用其他 JS 接口
$("[data-type]").on("click", function (e) {
    switch ($(e.target).attr("data-type")) {
        case "checkJsApi":
            ww.checkJsApi({
                jsApiList: jsApiList,
                success: function (res) {
                    alert(JSON.stringify(res));
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                }
            });
            break;
        case "getContext":
            ww.getContext({
                success: function (res) {
                    alert(JSON.stringify(res));
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                }
            });
            break;
        case "selectEnterpriseContact":
            ww.selectEnterpriseContact({
                fromDepartmentId: -1,   // -1 表示从自己所在部门开始
                mode: 'multi',
                type: ['user'], // ['department', 'user']
                success: function (res) {
                    userids.length = 0
                    res.result.userList.forEach(user => {
                        userids.push(user.id)
                    })
                    alert(JSON.stringify(res));
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                }
            });
            break;
        case "openUserProfile":
            if (userids.length == 0) {
                alert("先调用selectEnterpriseContact接口选中一个成员")
            }
            else {
                ww.openUserProfile({
                    type: 1,    // 1 是企业成员
                    userid: userids[0],
                    success: function (res) {
                        alert(JSON.stringify(res));
                    },
                    fail: function (res) {
                        alert(JSON.stringify(res));
                    }
                });
            }
            break;
        case "openEnterpriseChat":
            if (userids.length == 0) {
                alert("先调用selectEnterpriseContact接口选择成员")
            }
            else {
                ww.openEnterpriseChat({
                    userIds: userids,
                    fail: function (res) {
                        alert(JSON.stringify(res));
                    }
                });
            }
            break;
        case "onMenuShareAppMessage":
            ww.onMenuShareAppMessage(shareConfig);
            alert("已注册获取“转发给同事”状态事件");
            break;
        case "onMenuShareWechat":
            ww.onMenuShareWechat(shareConfig);
            alert("已注册获取“微信分享给朋友”状态事件");
            break;
        case "onMenuShareTimeline":
            ww.onMenuShareTimeline(shareConfig);
            alert("已注册获取“分享到朋友圈”状态事件");
            break;
        case "shareAppMessage":
            ww.shareAppMessage(shareConfig);
            break;
        case "shareWechatMessage":
            ww.shareWechatMessage(shareConfig);
            break;
        case "onHistoryBack":
            ww.onHistoryBack(function () {
                return confirm("确定要放弃当前页面的修改?");
            });
            alert("已注册获取“页面返回”状态事件");
            break;
        case "hideOptionMenu":
            ww.hideOptionMenu({
                success: function (res) {
                    alert("hideOptionMenu success!" + JSON.stringify(res));
                },
                fail: function (res) {
                    alert("hideOptionMenu fail!" + JSON.stringify(res));
                },
            });
            break;
        case "showOptionMenu":
            ww.showOptionMenu({
                success: function (res) {
                    alert("showOptionMenu success!" + JSON.stringify(res));
                },
                fail: function (res) {
                    alert("showOptionMenu fail!" + JSON.stringify(res));
                },
            });
            break;
        case "closeWindow":
            ww.closeWindow();
            break;
        case "hideMenuItems":
            ww.hideMenuItems({
                menuList: [
                    "menuItem:share:appMessage",
                    "menuItem:share:wechat",
                    "menuItem:favorite",
                ],
                success: function (e) {
                    alert("已隐藏“转发”,“微信”,“收藏”按钮");
                },
                fail: function (e) {
                    alert(JSON.stringify(e));
                },
            });
            break;
        case "showMenuItems":
            ww.showMenuItems({
                menuList: [
                    "menuItem:share:appMessage",
                    "menuItem:share:wechat",
                    "menuItem:favorite",
                ]
            });
            break;
        case "hideAllNonBaseMenuItem":
            ww.hideAllNonBaseMenuItem({
                success: function (res) {
                    alert("hideAllNonBaseMenuItem success!" + JSON.stringify(res));
                },
                fail: function (res) {
                    alert("hideAllNonBaseMenuItem fail!" + JSON.stringify(res));
                },
            });
            break;
        case "showAllNonBaseMenuItem":
            ww.showAllNonBaseMenuItem();
            break;
        case "openDefaultBrowser":
            ww.openDefaultBrowser({
                url: 'https://work.weixin.qq.com/',
                fail: function (res) {
                    alert("openDefaultBrowser fail!" + JSON.stringify(res));
                },
            });
            break;
        case "onUserCaptureScreen":
            ww.onUserCaptureScreen(function () {
                alert('用户截屏了')
            });
            break;
        case "scanQRCode":
            ww.scanQRCode({
                needResult: true,
                scanType: ['qrCode'],
                success: function (res) {
                    alert("scanQRCode success!" + JSON.stringify(res));
                },
                fail: function (res) {
                    alert("scanQRCode fail!" + JSON.stringify(res));
                },
                cancel: function (res) {
                    alert("scanQRCode cancel!" + JSON.stringify(res));
                }
            });
            break;
        case "enterpriseVerify":
            ww.enterpriseVerify({
                success: function (res) {
                    alert("enterpriseVerify success!" + JSON.stringify(res));
                },
                fail: function (res) {
                    alert("enterpriseVerify fail!" + JSON.stringify(res));
                }
            });
            break;
        case "openAppManage":
            ww.openAppManage({
                fail: function (res) {
                    alert("openAppManage fail!" + JSON.stringify(res));
                }
            });
            break;
        case "chooseImage":
            ww.chooseImage({
                count: 1,
                sizeType: ["original", "compressed"],
                sourceType: ["album", "camera"],
                success: function (res) {
                    images.localId = res.localIds;
                    alert("已选择 " + res.localIds.length + " 张图片");
                    // show images
                    $('#imageContainer').empty();
                    res.localIds.forEach(localId => {
                        const imgElement = document.createElement('img');
                        imgElement.src = localId;
                        imgElement.alt = 'Response_image';
                        imgElement.className = 'responsive_image';
                        $("#imageContainer").append(imgElement);
                    })
                },
                fail: function (res) {
                    alert("chooseImage fail!" + JSON.stringify(res));
                },
            });
            break;
        case "previewImage":
            ww.previewImage({
                current:
                    "http://demo.open.weixin.qq.com/jssdk/images/p2166127561.jpg",
                urls: [
                    "http://demo.open.weixin.qq.com/jssdk/images/p2166127561.jpg",
                    "https://image14.m1905.cn/uploadfile/2013/1119/20131119113613957229.jpg",
                    "https://img2.baidu.com/it/u=3076280632,1757512582&fm=253&fmt=auto&app=120&f=JPEG",
                ],
                fail: function (res) {
                    alert("previewImage fail!" + JSON.stringify(res));
                },
            });
            break;
        case "uploadImage":
            if (0 == images.localId.length)
                return void alert("请先使用 chooseImage 接口选择图片");
            var a = 0,
                o = images.localId.length;
            (images.serverId = []);
            (function e() {
                ww.uploadImage({
                    localId: images.localId[a],
                    success: function (s) {
                        a++, images.serverId.push(s.serverId);
                        a < o && e();
                    },
                    fail: function (res) {
                        alert(JSON.stringify(res));
                    },
                });
            })();
            break;
        case "downloadImage":
            if (0 === images.serverId.length)
                return void alert("请先使用 uploadImage 上传图片");
            (a = 0), (o = images.serverId.length);
            (images.localId = []);
            $('#imageContainerForDownload').empty();
            (function e() {
                ww.downloadImage({
                    serverId: images.serverId[a],
                    success: function (s) {
                        a++, alert("已下载:" + a + "/" + o);
                        images.localId.push(s.localId);
                        showDownloadImage(s.localId);
                        a < o && e();
                    },
                });
            })();
            break;
        case "startRecord":
            $('#voiceToast').text("正在录音,点击 stopRecord 完成录音...");
            ww.startRecord({
                cancel: function (res) {
                    alert("用户拒绝授权录音" + JSON.stringify(res));
                },
            });
            break;
        case "stopRecord":
            $('#voiceToast').empty();
            ww.stopRecord({
                success: function (res) {
                    voice.localId = res.localId;
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "playVoice":
            if ("" == voice.localId)
                return void alert("请先使用 startRecord 接口录制一段声音");
            ww.playVoice({
                localId: voice.localId,
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "pauseVoice":
            if ("" == voice.localId)
                return void alert("请先使用 startRecord 接口录制一段声音");
            ww.pauseVoice({
                localId: voice.localId,
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "stopVoice":
            if ("" == voice.localId)
                return void alert("请先使用 startRecord 接口录制一段声音");
            ww.stopVoice({
                localId: voice.localId,
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "uploadVoice":
            if ("" == voice.localId)
                return void alert("请先使用 startRecord 接口录制一段声音");
            ww.uploadVoice({
                localId: voice.localId,
                success: function (res) {
                    alert("上传语音成功,serverId 为" + res.serverId);
                    voice.serverId = res.serverId;
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "downloadVoice":
            if ("" == voice.serverId)
                return void alert("请先使用 uploadVoice 上传声音");
            ww.downloadVoice({
                serverId: voice.serverId,
                success: function (res) {
                    alert("下载语音成功,localId 为" + res.localId);
                    voice.localId = res.localId;
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "translateVoice":
            if ("" == voice.localId)
                return void alert("请先使用 startRecord 接口录制一段声音");
            ww.translateVoice({
                localId: voice.localId,
                success: function (res) {
                    alert("识别的文字为:" + res.translateResult);
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "createWWLoginPanel":
            ww.createWWLoginPanel({
                el: '#ww_login',
                params: {
                    login_type: 'CorpApp',
                    appid: corpId,
                    agentid: agentId,
                    redirect_uri: 'http://bendell02.top/amyu/hello', // change your domain url 
                    state: 'loginState',
                    redirect_type: 'self', // can be callback / top / self
                    panel_size: 'small'
                },
                onCheckWeComLogin({ isWeComLogin }) {
                    alert(isWeComLogin)
                },
                onLoginSuccess(code) {
                    alert(JSON.stringify(code))
                },
                onLoginFail(err) {
                    alert(JSON.stringify(err))
                },
            })
            break;
        case "previewFile":
            ww.previewFile({
                url: 'http://open.work.weixin.qq.com/wwopen/downloadfile/wwapi.zip',
                name: 'Android开发工具包集合',
                size: 22189,
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "setClipboardData":
            ww.setClipboardData({
                data: 'data',
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "getClipboardData":
            ww.getClipboardData({
                success: function (res) {
                    alert(JSON.stringify(res));
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "getNetworkType":
            ww.getNetworkType({
                success: function (res) {
                    alert(JSON.stringify(res));
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "onNetworkStatusChange":
            ww.onNetworkStatusChange(function (event) {
                alert(JSON.stringify(event))
            });
            break;
        case "openLocation":
            ww.openLocation({
                latitude: 23.099994,
                longitude: 113.32452,
                name: "TIT 创意园",
                address: "广州市海珠区新港中路 397 号",
                scale: 14,
                infoUrl: "http://weixin.qq.com",
            });
            break;
        case "getLocation":
            ww.getLocation({
                success: function (res) {
                    alert(JSON.stringify(res));
                },
                cancel: function (res) {
                    alert("用户拒绝授权获取地理位置" + JSON.stringify(res));
                },
            });
            break;
        case "startAutoLBS":
            ww.startAutoLBS({
                success: function (res) {
                    $('#locationToast').text("已开启持续定位...");
                },
                cancel: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "stopAutoLBS":
            ww.stopAutoLBS({
                success: function (res) {
                    $('#locationToast').empty();
                },
                cancel: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "onLocationChange":
            origin_text = $('#locationToast').text();
            ww.onLocationChange(function (res) {
                const latitude = res.latitude;
                const longitude = res.longitude;
                const accuracy = res.accuracy;
                $('#locationToast').text(origin_text + `纬度: ${latitude}, 经度: ${longitude}, 精度: ${accuracy}`);
            });
            break;
        case "selectExternalContact":
            ww.selectExternalContact({
                filterType: 0,
                success: function (res) {
                    alert(JSON.stringify(res));
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "shareToExternalContact":
            // 为防止滥用,同一个成员每日向一个客户最多可群发一条消息,每次群发最多可选 20000 个客户
            ww.shareToExternalContact({
                text: {
                    content: '企业微信'
                },
                attachments: [
                    {
                        msgtype: 'image',
                        image: {
                            imgUrl: 'https://res.mail.qq.com/node/ww/wwmng/style/images/index_share_logo$13c64306.png'
                        }
                    }
                ],
                success: function (res) {
                    alert(JSON.stringify(res));
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "shareToExternalChat":
            // 为防止滥用,同一个成员每日向一个客户最多可群发一条消息,每次群发最多可选 20000 个客户
            ww.shareToExternalChat({
                text: {
                    content: '企业微信'
                },
                attachments: [
                    {
                        msgtype: 'image',
                        image: {
                            imgUrl: 'https://res.mail.qq.com/node/ww/wwmng/style/images/index_share_logo$13c64306.png'
                        }
                    }
                ],
                success: function (res) {
                    alert(JSON.stringify(res));
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "navigateToAddCustomer":
            ww.navigateToAddCustomer({
                success: function (res) {
                    alert(JSON.stringify(res));
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "shareToExternalMoments":
            ww.shareToExternalMoments({
                text: {
                    content: '企业微信'
                },
                attachments: [
                    {
                        msgtype: 'image',
                        image: {
                            imgUrl: 'https://res.mail.qq.com/node/ww/wwmng/style/images/index_share_logo$13c64306.png'
                        }
                    }
                ],
                success: function (res) {
                    alert(JSON.stringify(res));
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "updateMomentsSetting":
            ww.updateMomentsSetting({
                signature: '个性签名',
                imgUrl: 'http://demo.open.weixin.qq.com/jssdk/images/p2166127561.jpg',
                success: function (res) {
                    alert(JSON.stringify(res));
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "checkSchedule":
            if (userids.length == 0) {
                alert("先调用selectEnterpriseContact接口选中一个成员")
                $('[data-type="selectEnterpriseContact"]')[0].scrollIntoView({
                    behavior: 'smooth'
                });
            }
            else {
                // 应用需具有日程使用权限
                ww.checkSchedule({
                    start_time: 1667232000,
                    end_time: 1667318400,
                    users: [userids[0]],
                    success: function (res) {
                        alert(JSON.stringify(res));
                    },
                    fail: function (res) {
                        alert(JSON.stringify(res));
                    },
                });
            }
            break;
        case "startMeeting":
            // 应用需具有日程使用权限
            if (userids.length == 0) {
                alert("先调用selectEnterpriseContact接口选择入会成员")
                $('[data-type="selectEnterpriseContact"]')[0].scrollIntoView({
                    behavior: 'smooth'
                });
            }
            else {
                ww.startMeeting({
                    meetingType: 1,
                    theme: '员工大会',
                    attendees: userids,
                    success: function (res) {
                        alert(JSON.stringify(res));
                    },
                    fail: function (res) {
                        alert(JSON.stringify(res));
                    },
                });
            }
            break;
        case "startLiving":
            // 应用需具有直播使用权限
            ww.startLiving({
                liveType: 1,
                theme: '新同学培训',
                success: function (res) {
                    alert(JSON.stringify(res));
                    livingIds.push(res.livingId);
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "replayLiving":
            // 应用需具有直播使用权限
            if (livingIds.length == 0) {
                alert("先调用 startLiving 开始一场直播")
            }
            else {
                ww.replayLiving({
                    livingId: livingIds[livingIds.length - 1],
                    success: function (res) {
                        alert(JSON.stringify(res));
                    },
                    fail: function (res) {
                        alert(JSON.stringify(res));
                    },
                });
            }
            break;
        case "downloadLivingReplay":
            // 应用需具有直播使用权限
            if (livingIds.length == 0) {
                alert("先调用 startLiving 开始一场直播")
            }
            else {
                ww.downloadLivingReplay({
                    livingId: livingIds[livingIds.length - 1],
                    success: function (res) {
                        alert(JSON.stringify(res));
                    },
                    fail: function (res) {
                        alert(JSON.stringify(res));
                    },
                });
            }
            break;
        case "thirdPartyOpenPage":
            // 应用需具有审批权限
            ww.thirdPartyOpenPage({
                oaType: '10001',
                // template_id 可在第三方应用-审批接口中创建模板获取
                templateId: '598180d72a842404ba872768c873a1a0_2131911712',
                thirdNo: 'thirdNo',
                extData: {
                    fieldList: [
                        {
                            type: 'text',
                            title: '采购类型',
                            value: '市场活动'
                        },
                        {
                            type: 'link',
                            title: '订单链接',
                            value: 'https://work.weixin.qq.com'
                        }
                    ]
                },
                success: function (res) {
                    alert(JSON.stringify(res));
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
    }
})

function showDownloadImage(localId) {
    ww.getLocalImgData({
        localId: localId,
        success: function (res) {
            const imgElement = document.createElement('img');

            // Get correct imageBase64
            const localData = res.localData;
            let imageBase64 = '';
            if (localData.indexOf('data:image') == 0) {
                //苹果的直接赋值,默认生成'data:image/jpeg;base64,'的头部拼接
                imageBase64 = localData;
            } else {
                //此处是安卓中的唯一得坑!在拼接前需要对localData进行换行符的全局替换
                //此时一个正常的base64图片路径就完美生成赋值到img的src中了
                imageBase64 = 'data:image/jpeg;base64,' + localData.replace(/\n/g, '');
            }

            imgElement.src = imageBase64;

            imgElement.alt = 'Response_image';
            imgElement.className = 'responsive_image';
            $("#imageContainerForDownload").append(imgElement);
        },
        fail: function (res) {
            alert("getLocalImgData fail!" + JSON.stringify(res));
        }
    })
}