企业微信 WECOM-JSSDK Demo

1. 说明

1.1 代码结构

企业微信官方有"前端 JS-SDK 演示工具": http://open.work.weixin.qq.com/api/jsapidemo

在这个基础上,仿照写了WECOM-JSSDK Demo。
代码结构

  • jsapi_demo.html
  • jsapi_demo_js.js
  • jsapi_demo_styles.css
  • zepto.v1.2.0.min.js (这个是直接下载的)

所有源码会在文章最后给出

1.2 效果

网页效果如下:


1.3 JSSDK说明

WECOM-JSSDK与JS-SDK的异同,官方说明如下

旧版本(原JS-SDK)仍支持使用,相较旧版本WECOM-JSSDK提供了Typescript类型支持、npm引入等新能力。

企业微信官方目前是推荐使用WECOM-JSSDK

WECOM-JSSDK能做什么, 官方说明如下
简而言之,WECOM-JSSDK为网页应用提供了调用原生能力的通道

网页应用通过 WECOM-JSSDK 可以调起拍照、选择图片、录音、获取地理位置信息等手机系统能力。
同时可以使用企业微信分享、扫一扫等企业微信微信特有能力,为企业微信用户提供更优质的网页应用体验。

使用WECOM-JSSDK最关键的一步就是鉴权,其他接口调用就是正常的api调用

2. 鉴权

2.1 使用说明

在调用 JSAPI 前,需要先通过 ww.register 注册当前页面的身份信息。身份信息分为两种:

  1. 企业身份与权限
  2. 应用(自建应用/第三方应用等)身份与权限

之所以有两种,是有些api使用企业身份即可,有些api需要使用应用身份。总体应用身份能做的事情更多一些。

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

2.2 前端注册

代码里前端注册的代码如下:

// basic info
corpId = 'your_corp_id';
agentId = 1000005;
jsApiList = ['checkJsApi', 'getContext', 'selectEnterpriseContact',];
    
// 应用身份与权限
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 }
}

企业身份注册去掉getAgentConfigSignature部分内容即可

其中jsApiList是需要使用的JSAPI列表,用哪个就添加进去

在这里,签名是通过POST请求到后端实现的

2.3 后端生成签名

后端是用FastAPI搭建的,获取签名的代码如下:

def create_nonce_str(length=16):
    return "".join(
        random.choice(string.ascii_letters + string.digits) for _ in range(length)
    )


def create_signature(jsapi_ticket, noncestr, timestamp, url):
    string = f"jsapi_ticket={jsapi_ticket}&noncestr={noncestr}&timestamp={timestamp}&url={url}"
    return hashlib.sha1(string.encode("utf-8")).hexdigest()


@amyu.post("/get_corp_jssdk_config")
async def get_corp_jssdk_config(request: Request):
    data = await request.json()
    url = data.get("url")

    jsapi_ticket = contact.get_corp_jsapi_ticket()
    noncestr = create_nonce_str()
    timestamp = str(int(time.time()))
    signature = create_signature(jsapi_ticket, noncestr, timestamp, url)

    return {
        "timestamp": timestamp,
        "nonceStr": noncestr,
        "signature": signature,
    }


@amyu.post("/get_app_jssdk_config")
async def get_app_jssdk_config(request: Request):
    data = await request.json()
    url = data.get("url")

    jsapi_ticket = contact.get_app_jsapi_ticket()
    noncestr = create_nonce_str()
    timestamp = str(int(time.time()))
    signature = create_signature(jsapi_ticket, noncestr, timestamp, url)

    return {
        "timestamp": timestamp,
        "nonceStr": noncestr,
        "signature": signature,
    }

其中获取jsapi_ticketcontact.get_app_jsapi_ticket()contact.get_corp_jsapi_ticket()是根据access_token去GET企业微信服务端API获取的。
这里的实现各有各的差异,由于处理缓存token的代码比较多,这里就没有给出具体的代码。

具体可参考: https://developer.work.weixin.qq.com/document/path/96909

3. 源码

3.1 jsapi_demo.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0,user-scalable=0">
    <title>企业微信 WECOM-JSSDK Demo</title>
    <link rel="stylesheet" href="jsapi_demo_styles.css" />
</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>
            <!-- <span class="desc">监听截屏事件</span>
            <button class="btn btn_primary" data-type="onUserCaptureScreen">onUserCaptureScreen</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>

</body>

<script src="https://wwcdn.weixin.qq.com/node/open/js/wecom-jssdk-2.0.2.js"></script>
<script type="text/javascript" src="zepto.v1.2.0.min.js"></script>
<script type="text/javascript" src="jsapi_demo_js.js?v=1.0.3"></script>

</html>

3.2 jsapi_demo_js.js


// 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));
        }
    })
}

3.3 jsapi_demo_styles.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;
    margin-bottom: 15px;
    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;
    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);
}

.image_container {
    width: 100%;
    text-align: center;
}

.responsive_image {
    width: 100%;
    height: auto;
    display: block;
}

3.4 其他

zepto.v1.2.0.min.js
是从zepto官网下载的https://zeptojs.com/

下载之后把zepto.min.js重命名为zepto.v1.2.0.min.js

后端代码就太多了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容