企業(yè)微信 WECOM-JSSDK Demo

1. 說明

1.1 代碼結(jié)構(gòu)

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

在這個基礎(chǔ)上,仿照寫了WECOM-JSSDK Demo。
代碼結(jié)構(gòu)

  • jsapi_demo.html
  • jsapi_demo_js.js
  • jsapi_demo_styles.css
  • zepto.v1.2.0.min.js (這個是直接下載的)

所有源碼會在文章最后給出

1.2 效果

網(wǎng)頁效果如下:


1.3 JSSDK說明

WECOM-JSSDK與JS-SDK的異同,官方說明如下

舊版本(原JS-SDK)仍支持使用,相較舊版本W(wǎng)ECOM-JSSDK提供了Typescript類型支持、npm引入等新能力。

企業(yè)微信官方目前是推薦使用WECOM-JSSDK

WECOM-JSSDK能做什么, 官方說明如下
簡而言之,WECOM-JSSDK為網(wǎng)頁應(yīng)用提供了調(diào)用原生能力的通道

網(wǎng)頁應(yīng)用通過 WECOM-JSSDK 可以調(diào)起拍照、選擇圖片、錄音、獲取地理位置信息等手機系統(tǒng)能力。
同時可以使用企業(yè)微信分享、掃一掃等企業(yè)微信微信特有能力,為企業(yè)微信用戶提供更優(yōu)質(zhì)的網(wǎng)頁應(yīng)用體驗。

使用WECOM-JSSDK最關(guān)鍵的一步就是鑒權(quán),其他接口調(diào)用就是正常的api調(diào)用

2. 鑒權(quán)

2.1 使用說明

在調(diào)用 JSAPI 前,需要先通過 ww.register 注冊當(dāng)前頁面的身份信息。身份信息分為兩種:

  1. 企業(yè)身份與權(quán)限
  2. 應(yīng)用(自建應(yīng)用/第三方應(yīng)用等)身份與權(quán)限

之所以有兩種,是有些api使用企業(yè)身份即可,有些api需要使用應(yīng)用身份??傮w應(yīng)用身份能做的事情更多一些。

注冊身份信息時,需要提供對應(yīng)的簽名函數(shù),簽名是根據(jù)當(dāng)前頁面的 URL 和 jsapi_ticket 等信息生成的。
jsapi_ticket敏感信息,需要在服務(wù)端完成簽名操作。

2.2 前端注冊

代碼里前端注冊的代碼如下:

// basic info
corpId = 'your_corp_id';
agentId = 1000005;
jsApiList = ['checkJsApi', 'getContext', 'selectEnterpriseContact',];
    
// 應(yīng)用身份與權(quán)限
ww.register({
    corpId: corpId,         // 必填,當(dāng)前用戶企業(yè)所屬企業(yè)ID
    agentId: agentId,       // 必填,當(dāng)前應(yīng)用的AgentID
    jsApiList: jsApiList,   // 必填,需要使用的JSAPI列表
    getConfigSignature,     // 必填,根據(jù)url生成企業(yè)簽名的回調(diào)函數(shù)
    getAgentConfigSignature // 必填,根據(jù)url生成應(yīng)用簽名的回調(diào)函數(shù)
})

async function getConfigSignature(url) {
    // 根據(jù) url 生成企業(yè)簽名
    // 生成方法參考 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) {
    // 根據(jù) url 生成應(yīng)用簽名,生成方法同上,但需要使用應(yīng)用的 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 }
}

企業(yè)身份注冊去掉getAgentConfigSignature部分內(nèi)容即可

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

在這里,簽名是通過POST請求到后端實現(xiàn)的

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()是根據(jù)access_token去GET企業(yè)微信服務(wù)端API獲取的。
這里的實現(xiàn)各有各的差異,由于處理緩存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>企業(yè)微信 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">基礎(chǔ)接口</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">系統(tǒng)界面接口</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">網(wǎng)絡(luò)接口</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">客戶聯(lián)系接口</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">基礎(chǔ)接口</h3>
            <span class="desc">判斷當(dāng)前客戶端是否支持指定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">調(diào)起個人信息頁面</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">監(jiān)聽「轉(zhuǎn)發(fā)」按鈕點擊</span>
            <button class="btn btn_primary" data-type="onMenuShareAppMessage">onMenuShareAppMessage</button>
            <span class="desc">監(jiān)聽「微信」按鈕點擊</span>
            <button class="btn btn_primary" data-type="onMenuShareWechat">onMenuShareWechat</button>
            <span class="desc">監(jiān)聽「朋友圈」按鈕點擊</span>
            <button class="btn btn_primary" data-type="onMenuShareTimeline">onMenuShareTimeline</button>
            <span class="desc">自定義轉(zhuǎn)發(fā)到會話</span>
            <button class="btn btn_primary" data-type="shareAppMessage">shareAppMessage</button>
            <span class="desc">自定義轉(zhuǎn)發(fā)到微信</span>
            <button class="btn btn_primary" data-type="shareWechatMessage">shareWechatMessage</button>

            <h3 id="menu-webview">界面接口</h3>
            <span class="desc">監(jiān)聽頁面返回</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">關(guān)閉當(dāng)前窗口</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">隱藏非基礎(chǔ)按鈕</span>
            <button class="btn btn_primary" data-type="hideAllNonBaseMenuItem">hideAllNonBaseMenuItem</button>
            <span class="desc">顯示非基礎(chǔ)按鈕</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">監(jiān)聽截屏事件</span>
            <button class="btn btn_primary" data-type="onUserCaptureScreen">onUserCaptureScreen</button> -->

            <h3 id="menu-sys-webview">系統(tǒng)界面接口</h3>
            <span class="desc">調(diào)起掃一掃</span>
            <button class="btn btn_primary" data-type="scanQRCode">scanQRCode</button>
            <span class="desc">跳轉(zhuǎn)認證界面</span>
            <button class="btn btn_primary" data-type="enterpriseVerify">enterpriseVerify</button>
            <span class="desc">調(diào)起應(yīng)用管理界面</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">預(yù)覽圖片</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">語音轉(zhuǎn)文字</span>
            <button class="btn btn_primary" data-type="translateVoice"> translateVoice </button>

            <h3 id="menu-open">開放接口</h3>
            <span class="desc">創(chuàng)建企業(yè)微信登錄面板</span>
            <button class="btn btn_primary" data-type="createWWLoginPanel"> createWWLoginPanel </button>
            <div id="ww_login"></div>

            <h3 id="menu-file">文件接口</h3>
            <span class="desc">預(yù)覽文件</span>
            <button class="btn btn_primary" data-type="previewFile"> previewFile </button>

            <h3 id="menu-clipboard">剪貼板接口</h3>
            <span class="desc">設(shè)置剪貼板內(nèi)容</span>
            <button class="btn btn_primary" data-type="setClipboardData"> setClipboardData </button>
            <span class="desc">獲取剪貼板內(nèi)容</span>
            <button class="btn btn_primary" data-type="getClipboardData"> getClipboardData </button>

            <h3 id="menu-network">網(wǎng)絡(luò)接口</h3>
            <span class="desc">獲取網(wǎng)絡(luò)狀態(tài)</span>
            <button class="btn btn_primary" data-type="getNetworkType"> getNetworkType </button>
            <span class="desc">監(jiān)聽網(wǎng)絡(luò)狀態(tài)</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">打開內(nèi)置地圖</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">打開持續(xù)定位</span>
            <button class="btn btn_primary" data-type="startAutoLBS"> startAutoLBS </button>
            <span class="desc">停止持續(xù)定位</span>
            <button class="btn btn_primary" data-type="stopAutoLBS"> stopAutoLBS </button>
            <span class="desc">監(jiān)聽地理位置</span>
            <button class="btn btn_primary" data-type="onLocationChange"> onLocationChange </button>

            <h3 id="menu-client">客戶聯(lián)系接口</h3>
            <span class="desc">調(diào)起外部聯(lián)系人列表</span>
            <button class="btn btn_primary" data-type="selectExternalContact"> selectExternalContact </button>
            <span class="desc">群發(fā)消息給客戶</span>
            <button class="btn btn_primary" data-type="shareToExternalContact"> shareToExternalContact </button>
            <span class="desc">群發(fā)消息到客戶群</span>
            <button class="btn btn_primary" data-type="shareToExternalChat"> shareToExternalChat </button>
            <span class="desc">調(diào)起添加客戶界面</span>
            <button class="btn btn_primary" data-type="navigateToAddCustomer"> navigateToAddCustomer </button>
            <span class="desc">發(fā)表內(nèi)容到客戶朋友圈</span>
            <button class="btn btn_primary" data-type="shareToExternalMoments"> shareToExternalMoments </button>
            <span class="desc">設(shè)置朋友圈封面與簽名</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">創(chuàng)建會議并調(diào)起會議室頁面</span>
            <button class="btn btn_primary" data-type="startMeeting"> startMeeting </button>

            <h3 id="menu-living">直播接口</h3>
            <span class="desc">創(chuàng)建直播并調(diào)起直播頁面</span>
            <button class="btn btn_primary" data-type="startLiving"> startLiving </button>
            <span class="desc">調(diào)起直播間回放</span>
            <button class="btn btn_primary" data-type="replayLiving"> replayLiving </button>
            <span class="desc">調(diào)起直播回放下載頁面</span>
            <button class="btn btn_primary" data-type="downloadLivingReplay"> downloadLivingReplay </button>

            <h3 id="menu-approval">審批接口</h3>
            <span class="desc">應(yīng)用發(fā)起審批</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',];

// // 企業(yè)身份與權(quán)限
// ww.register({
//     corpId: corpId,       // 必填,當(dāng)前用戶企業(yè)所屬企業(yè)ID
//     jsApiList: jsApiList, // 必填,需要使用的JSAPI列表
//     getConfigSignature    // 必填,根據(jù)url生成企業(yè)簽名的回調(diào)函數(shù)
// })

// 應(yīng)用身份與權(quán)限
ww.register({
    corpId: corpId,         // 必填,當(dāng)前用戶企業(yè)所屬企業(yè)ID
    agentId: agentId,       // 必填,當(dāng)前應(yīng)用的AgentID
    jsApiList: jsApiList,   // 必填,需要使用的JSAPI列表
    getConfigSignature,     // 必填,根據(jù)url生成企業(yè)簽名的回調(diào)函數(shù)
    getAgentConfigSignature // 必填,根據(jù)url生成應(yīng)用簽名的回調(diào)函數(shù)
})

async function getConfigSignature(url) {
    // 根據(jù) url 生成企業(yè)簽名
    // 生成方法參考 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) {
    // 根據(jù) url 生成應(yīng)用簽名,生成方法同上,但需要使用應(yīng)用的 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: "互聯(lián)網(wǎng)之子",
    desc: "在長大的過程中,我才慢慢發(fā)現(xiàn),我身邊的所有事,別人跟我說的所有事,那些所謂本來如此,注定如此的事,它們其實沒有非得如此,事情是可以改變的。更重要的是,有些事既然錯了,那就該做出改變。",
    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));
    },
};

// 調(diào)用 register 后可以立刻調(diào)用其他 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("先調(diào)用selectEnterpriseContact接口選中一個成員")
            }
            else {
                ww.openUserProfile({
                    type: 1,    // 1 是企業(yè)成員
                    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("先調(diào)用selectEnterpriseContact接口選擇成員")
            }
            else {
                ww.openEnterpriseChat({
                    userIds: userids,
                    fail: function (res) {
                        alert(JSON.stringify(res));
                    }
                });
            }
            break;
        case "onMenuShareAppMessage":
            ww.onMenuShareAppMessage(shareConfig);
            alert("已注冊獲取“轉(zhuǎn)發(fā)給同事”狀態(tài)事件");
            break;
        case "onMenuShareWechat":
            ww.onMenuShareWechat(shareConfig);
            alert("已注冊獲取“微信分享給朋友”狀態(tài)事件");
            break;
        case "onMenuShareTimeline":
            ww.onMenuShareTimeline(shareConfig);
            alert("已注冊獲取“分享到朋友圈”狀態(tài)事件");
            break;
        case "shareAppMessage":
            ww.shareAppMessage(shareConfig);
            break;
        case "shareWechatMessage":
            ww.shareWechatMessage(shareConfig);
            break;
        case "onHistoryBack":
            ww.onHistoryBack(function () {
                return confirm("確定要放棄當(dāng)前頁面的修改?");
            });
            alert("已注冊獲取“頁面返回”狀態(tài)事件");
            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("已隱藏“轉(zhuǎn)發(fā)”,“微信”,“收藏”按鈕");
                },
                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("用戶拒絕授權(quán)錄音" + 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開發(fā)工具包集合',
                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 創(chuàng)意園",
                address: "廣州市海珠區(qū)新港中路 397 號",
                scale: 14,
                infoUrl: "http://weixin.qq.com",
            });
            break;
        case "getLocation":
            ww.getLocation({
                success: function (res) {
                    alert(JSON.stringify(res));
                },
                cancel: function (res) {
                    alert("用戶拒絕授權(quán)獲取地理位置" + JSON.stringify(res));
                },
            });
            break;
        case "startAutoLBS":
            ww.startAutoLBS({
                success: function (res) {
                    $('#locationToast').text("已開啟持續(xù)定位...");
                },
                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}, 經(jīng)度: ${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":
            // 為防止濫用,同一個成員每日向一個客戶最多可群發(fā)一條消息,每次群發(fā)最多可選 20000 個客戶
            ww.shareToExternalContact({
                text: {
                    content: '企業(yè)微信'
                },
                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":
            // 為防止濫用,同一個成員每日向一個客戶最多可群發(fā)一條消息,每次群發(fā)最多可選 20000 個客戶
            ww.shareToExternalChat({
                text: {
                    content: '企業(yè)微信'
                },
                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: '企業(yè)微信'
                },
                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("先調(diào)用selectEnterpriseContact接口選中一個成員")
                $('[data-type="selectEnterpriseContact"]')[0].scrollIntoView({
                    behavior: 'smooth'
                });
            }
            else {
                // 應(yīng)用需具有日程使用權(quán)限
                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":
            // 應(yīng)用需具有日程使用權(quán)限
            if (userids.length == 0) {
                alert("先調(diào)用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":
            // 應(yīng)用需具有直播使用權(quán)限
            ww.startLiving({
                liveType: 1,
                theme: '新同學(xué)培訓(xùn)',
                success: function (res) {
                    alert(JSON.stringify(res));
                    livingIds.push(res.livingId);
                },
                fail: function (res) {
                    alert(JSON.stringify(res));
                },
            });
            break;
        case "replayLiving":
            // 應(yīng)用需具有直播使用權(quán)限
            if (livingIds.length == 0) {
                alert("先調(diào)用 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":
            // 應(yīng)用需具有直播使用權(quán)限
            if (livingIds.length == 0) {
                alert("先調(diào)用 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":
            // 應(yīng)用需具有審批權(quán)限
            ww.thirdPartyOpenPage({
                oaType: '10001',
                // template_id 可在第三方應(yīng)用-審批接口中創(chuàng)建模板獲取
                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官網(wǎng)下載的https://zeptojs.com/

下載之后把zepto.min.js重命名為zepto.v1.2.0.min.js

后端代碼就太多了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容