浏览器唤醒 app 解决方案汇总

邓草
• 阅读 683

原理及说明: 首先:无论 IOS 还是 Android,浏览器无法预知本地是否安装了某个 APP。 功能实现的本质是浏览器通过 URL_scheme 打开 APP。Twitter 就注册自己能被「twitter://」打开。 补充:如果是 APP 之间的互相跳转是很简单的:IOS 可以使用 UIApplication 的 canOpenUrl 方法来监测 URL_scheme 是否能打开对应的 APP。exp:如果「twitter://」检测能被打开,也就说明本地安装了 Twitter 。再用 UIApplication 的 openURL 方法,就能打开 Twitter 了。Android 中的做法类似。

实现方案:以 IOS9 为一个分水岭:

iOS7/iOS8: iOS 中默认通过 Safari 打开 URL scheme,方法一般如下两种:

1,直跳方式:点击链接、修改 window.location 等。 唤醒你的 APP 或者: window.location.href = schemeUrl;

PS:如果 APP 唤醒失败或者未安装的情况,有可能会调到 404 页面。很影响用户体验。一般情况会让他跳到其他页面或者下载 APP

2,iframe 方式:在 body 上添加 iframe,设置src属性为跳转的URL scheme。 这种方法不会引起页面可见的变化(页面变成一个新页面),不会导致浏览器的历史记录的变化。 实现思路:

下载或打开APP

实现思路:点击 a 标签,首先会尝试打开 URL scheme,如果成功,就唤起 APP;如果失败,则跳转到 href 属性,即下载页。 (该方案在很多安卓机型有问题) Android

$('a').click(function() { location.href = '自定义 URL scheme'; t = Date.now(); setTimeout(function(){ if (Date.now() - t < 1200) { location.href = 'Android 下载地址'; } }, 1000); return false; } 理想过程是这样:浏览器尝试打开 URL scheme,在 1 秒计时后,检查当前时间,如果实际时间已过 1200 毫秒,说明唤起 APP 成功(唤起 APP 会让浏览器的定时器变慢);如果没超过 1200 毫秒,很可能是没有安装应用,就跳到下载地址。 或者:

var ifr = document.createElement('iframe'); ifr.src = 'com.baidu.tieba://'; ifr.style.display = 'none'; document.body.appendChild(ifr); var openTime = +new Date(); window.setTimeout(function(){ document.body.removeChild(ifr); if( (+new Date()) - openTime > 2500 ){ window.location = 'http://exam.com/xxxx.apk'; } },2000) 这种方案不稳定:因为 Android 是基于 Linux 的分时多任务的,setTimeout 的基准偏差可能会没那么大。

但如果设置比较小的运行间隔(<30ms),在浏览器或者 webview 中,应用切换到后台,setInterval 会被很明显的延迟执行,比如设置一个运行间隔 20ms,总计运行 100 次的定时器,如果页面一直处于前台,则 100 次跑完,总耗时与 100x20=2000ms 不会有太大差异,但页面在后台运行时,此时间会明显超过 2000ms。可以利用这一点来实现是否成功打开 APP 检测及回调。

function openApp(openUrl, appUrl, action, callback) { //检查app是否打开 function checkOpen(cb){ var _clickTime = +(new Date()); function check(elsTime) { if ( elsTime > 3000 || document.hidden || document.webkitHidden) { cb(1); } else { cb(0); } } //启动间隔20ms运行的定时器,并检测累计消耗时间是否超过3000ms,超过则结束 var _count = 0, intHandle; intHandle = setInterval(function(){ _count++;
var elsTime = +(new Date()) - _clickTime; if (_count>=100 || elsTime > 3000 ) { clearInterval(intHandle); check(elsTime); } }, 20); }

//在iframe 中打开APP
var ifr = document.createElement('iframe');
ifr.src = openUrl;
ifr.style.display = 'none';
if (callback) {
    checkOpen(function(opened){
        callback && callback(opened);
    });
}

document.body.appendChild(ifr);      
setTimeout(function() {
    document.body.removeChild(ifr);
}, 2000);  

} 可以通过 document.hidden 或 document.[webkit|moz|ms] Hidden 来判断页面是否被置入后台(即应用被唤起),或 visibilitychange 事件,但对于 Android 4.4 版本一下则不支持。

iOS9 在 iOS 9 上,iframe 方案变得不可用。 按不能使用之前 Android 的代码,因为在打开自定义 URL scheme 时,会弹出对话框,询问是否用 xx 应用来打开。往往用户还没来得及点击打开,定时器又触发了,导致跳到 App Store。 可以在尝试打开 URL scheme 后,再加一个页面跳转,这样对话框会被覆盖,再刷新页面,就能无需确认唤起 APP:

$('a').click(function() { location.href = '自定义 URL scheme'; location.href = '下载页'; location.reload(); } 下载页延时 2 秒跳转到 App Store。

APP 已安装这是没问题的,但如果 APP 未安装,跳 App Store 的请求会失败。 这时可以使用两个定时器:

$('a').click(function() { location.href = '自定义 URL scheme'; setTimeout(function() { location.href = '下载页'; }, 250); setTimeout(function() { location.reload(); }, 1000); } 不过在 iOS9 中其实是支持 universal link 的,就是一个 http 域名形式,在微信中都可以唤起 APP。如果未安装的话,可以直接引导用户去 APP store 下载。

PS:没有完美的解决方案。

微信中打开 因为微信将唤起本地 APP 的接口给禁了,所以微信中是不能直接唤起 APP 的,一般做法是提示用户在浏览器中打开,之后的流程还是我们上面讲的内容。

但是,在 iOS9 中,这个限制是可以突破的,也就是说可以直接唤起 APP。方法就是使用我们上文提到的 universal link。

在 Android 和 iOS8 及其以下系统中,我们可以利用腾讯的亲儿子:应用宝。把你的唤起地址配置成你 APP 的应用宝地址,微信中跳转到这个地址后,如果用户已经安装了 APP,则可直接唤起,如果没有安装,则可直接点击下载。 PS:一般需要一个中间页面引导用户在尾部浏览器打开。微信唤醒 APP 默认只能到达首页。

需要判断的使用场景: 1,用户是在手机浏览器打开 2,微信浏览器打开 3,PC 中打开 4,universal link 是否被关闭 。。。

其他实现方案: 魔窗的 mLink。只要你加了魔窗的 sdk,就可以通过类似 “https://s.mlinks.cc/AA01” 的链接,在任何环境下打开你的 APP,兼容超过 600 台以上安卓机型的第三方主流浏览器。不管是在手机浏览器中,还是在微信中打开,你可以指定唤起 APP 后直达 APP 中的某个页面或内容(某个促销商品等),就算用户没安装 APP,点击下载安装之后,再打开,还是跳转到指定的页面。

魔窗使用教程: 需要准备的材料: 1,微信分享 AppID 2,应用宝微下载链接 3,IOS :Bundle ID、URI Scheme、下载地址、Team ID(如果配置了 Universal Link) 4,Android: 包名、URI Scheme、下载地址

后台生成极光魔链位信息 JS 使用: 1,直接引用 mLink JS 文件。不要下载之后放入项目

2,在 HTML 页面中准备一个或多个用于打开 APP 的 a 元素 打开 APP

极光魔链后台配置好 mLink 必要参数并且生成一条短链

图片.png 初始化 mLink new Mlink({ mlink:'https://a.mlinks.cc/ABCD',//短链地址 button:document.querySelector('a#btnOpenApp') }); /* ------ 或 -------- */ var link='https://a.mlinks.cc/ABCD';//短链地址 var btn_1=document.querySelector('a#btnOpenApp1'); var btn_2=document.querySelector('a#btnOpenApp2'); var btn_3=document.querySelector('a#btnOpenApp3');

var options = [ { mlink: link+'?name=1', button: btn_1 }, { mlink:link+'?name=2', button: btn_2 }, { mlink: link+'?name=3', button: btn_3 } ];

new Mlink(options); 在 H5 页面中使用默认短链接进行动态传参

图片.png options 选项 { mlink: "短链KEY", button: document.querySelector('a#btnOpenApp'), autoLaunchApp : false, autoRedirectToDownloadUrl: true, downloadWhenUniversalLinkFailed: false, inapp : false, params: {} } 具体参数含义见链接:

补充: 京东兼容性解决方案:

(function(){ // 判断浏览器 var Navigator = navigator.userAgent; var ifChrome = Navigator.match(/Chrome/i) != null && Navigator.match(/Version/\d+.\d+(.\d+)?\sChrome//i) == null ? true : false;
var ifAndroid = (Navigator.match(/(Android);?[\s/]+([\d.]+)?/)) ? true : false; var ifiPad = (Navigator.match(/(iPad).*OS\s([\d_]+)/)) ? true : false; var ifiPhone = (!ifiPad && Navigator.match(/(iPhone\sOS)\s([\d_]+)/)) ? true : false; var ifSafari = (ifiPhone || ifiPad) && Navigator.match(/Safari/); var version = 0; ifSafari && (version = Navigator.match(/Version/([\d.]+)/));

        version = parseFloat(version[1], 10);
        // 是否从微信打开
    var ifWeixin = navigator.userAgent.indexOf("MicroMessenger") >= 0; // weixin
    var j = false;
    var iframe = "plugIn_downloadAppPlugIn_loadIframe";
    var t = false;
    var i = 0;
    var B = {};
    var b = {};
    var selector = null;
    var Hquery = {};
        // 判断当前使用的js框架是zepto还是jquery
    var Query = window.Zepto || window.jQuery ? true : false;
    var g = [];
        // 是否存在html5的localStorage 存储
    var v = window.localStorage ? true : false;
    var o = "mdownloadAppPlugInskip";
    var p = null;

    function m() { // 打印时间 例如:2016-5-18
        var M = new Date();
        var N = M.getFullYear();
        var O = M.getMonth() + 1;
        var L = M.getDate();
        strDate = N + "-" + O + "-" + L;
        return strDate
    }
        // 微信相关操作
    function r() { // weixin api
        WeixinJSBridge.invoke("getInstallState", {
            packageName: "com.jingdong.app.mall",
            packageUrl: "openApp.jdMobile://"
        }, function(M) {
            var N = M.err_msg,
                L = 0;
            if (N.indexOf("get_install_state:yes") > -1) {
                j = true
            }
        })
    }
       // 根据是否存在js框架进行dom和时间的绑定
    function bind(dom, event, fun) { // bind event
        if (Query) {
            selector("#" + dom).bind(event, fun)
        } else {
            selector("#" + dom).addEventListener(event, fun, !1)
        }
    }

    function z(L) {
        var M = (L || "mGen") + (++i);
        return M
    }
        // 微信操作
    if (ifWeixin) { // if navigitor is weixin 
        if (window.WeixinJSBridge && WeixinJSBridge.invoke) {
            r()
        } else {
            document.addEventListener("WeixinJSBridgeReady", r, !1)
        }
    }

        // 如果存在js框架
    if (Query) {
        selector = window.$;
        Hquery = window.$
    } else {
        selector = function(obj) {
            if (typeof obj == "object") {
                return obj
            }
            return document.querySelector(obj);
        };
        if (!window.$) {
            window.$ = Hquery = selector
        } else {
            Hquery = window.$ 
        }
    }
    window.onblur = function() {
        for (var L = 0; L < g.length; L++) {
            clearTimeout(g[L])
        }
    };
         // 设置cookie。
    function e(N) {
        var M = document.cookie.indexOf(N + "=");
        if (M == -1) {
            return ""
        }
        M = M + N.length + 1;
        var L = document.cookie.indexOf(";", M);
        if (L == -1) {
            L = document.cookie.length
        }
        return document.cookie.substring(M, L)
    }
        // 设置cookie
    function l(N, P, L, Q, O) { 
        var R = N + "=" + escape(P);
        if (L != "") {
            var M = new Date();
            M.setTime(M.getTime() + L * 24 * 3600 * 1000);
            R += ";expires=" + M.toGMTString()
        }
        if (Q != "") {
            R += ";path=" + Q
        }
        if (O != "") {
            R += ";domain=" + O
        }
        document.cookie = R
    }

        // 打开的链接集合
    function F(L) {
        var url = {
            downAppURl: "http://h5.m.jd.com/active/download/download.html?channel=jd-m",
            downAppIos: "http://union.m.jd.com/download/go.action?to=http%3A%2F%2Fitunes.apple.com%2Fcn%2Fapp%2Fid414245413&client=apple&unionId=12532&subunionId=m-top&key=e4dd45c0f480d8a08c4621b4fff5de74",
            downWeixin: "http://a.app.qq.com/o/simple.jsp?pkgname=com.jingdong.app.mall&g_f=991850",
            downIpad: "https://itunes.apple.com/cn/app/jing-dong-hd/id434374726?mt=8",
            inteneUrl: "openApp.jdMobile://360buy?type=1",
            inteneUrlParams: null,
            openAppBtnId: "",
            closePanelBtnId: "",
            closePanelId: "",
            closeCallblack: null,
            closeCallblackSource: null,
            cookieFlag: null,
            noRecord: false,
            sourceType: "JSHOP_SOURCE_TYPE",
            sourceValue: "JSHOP_SOURCE_VALUE",
            openAppEventId: "MDownLoadFloat_OpenNow",
            closePanelEventId: "MDownLoadFloat_Close"
        };
        if (L) {
            for (var M in L) {
                if (M && L[M]) {
                    url[M] = L[M]
                }
            }
        }
        return url
    }
        // 敲黑板 重点内容。看京东是怎么解决兼容问题的。
    function openApp(N, L) { // openApp
        var R = h(N); //获取相对应的url
        var O = null;
        if (ifWeixin) { // 如果是微信端
            var M = null;
            if (j) {
                M = R
            } else {
                M = N.downWeixin
            }
            location.href = M; // 直接使用location.href打开
            return
        }
        if (ifiPad) { // 如果是ipad
            O = N.downIpad
        } else { 
            if (ifiPhone) { // 如果是iphone
                O = N.downAppIos
            } else {
                O = N.downAppURl
            }
        }

        if (ifChrome) { // 如果是chrome
            if (ifAndroid) { //安卓浏览器
                var Q = R;
                R = y(Q);
                                // 延后50毫秒
                setTimeout(function() {
                    window.location.href = R 
                }, 50)
            }
        }
        if (ifSafari && version >= 9) { // 判断safari版本 如果大于9
            setTimeout(function() {  // 必须要使用settimeout
                var S = document.createElement("a"); //创建a元素
                S.setAttribute("href", R), S.style.display = "none", document.body.appendChild(S);
                var T = document.createEvent("HTMLEvents"); // 返回新创建的 Event 对象,具有指定的类型。
                T.initEvent("click", !1, !1)// 初始化新事件对象的属性,   S.dispatchEvent(T)  // 绑定事件
            }, 0)
        } else {
            document.querySelector("#" + iframe).src = R // 将iframe增加src
        }
        var P = Date.now();
        setTimeout(function() {
            if (L) {
                var S = setTimeout(function() {
                    x(P, O)
                }, 1500);
                g.push(S)
            }
        }, 100)
    }
        // x方法
    function x(N, downUrl) {
        var L = Date.now();
        if (N && (L - N) < (1500 + 200)) {
            window.location.href = downUrl
        }
    }

    function h(N) {
        var V = [];
        var P = N.inteneUrlParams;
        var T = {
            category: "jump",
            des: "productDetail"
        };
        if (N.sourceType && N.sourceValue) {
            T.sourceType = N.sourceType;
            T.sourceValue = N.sourceValue;
            if (P && !P.sourceType && !P.sourceValue) {
                P.sourceType = N.sourceType;
                P.sourceValue = N.sourceValue
            }
        }
        if (P) {
            for (var U in P) {
                if (U && P[U]) {
                    V.push('"' + U + '":"' + P[U] + '"')
                }
            }
        } else {
            for (var U in T) {
                if (U && T[U]) {
                    V.push('"' + U + '":"' + T[U] + '"')
                }
            }
        }
        try {
            var Q = MPing.EventSeries.getSeries();
            if (Q) {
                var W = JSON.parse(Q);
                W.jdv = encodeURIComponent(e("__jdv"));
                W.unpl = encodeURIComponent(e("unpl"));
                W.mt_xid = encodeURIComponent(e("mt_xid"));
                W.mt_subsite = encodeURIComponent(e("mt_subsite"))
            }
            var S = {
                mt_subsite: encodeURIComponent(e("mt_subsite")),
                __jdv: encodeURIComponent(e("__jdv")),
                unpl: encodeURIComponent(e("unpl")),
                __jda: encodeURIComponent(e("__jda"))
            };
            Q = JSON.stringify(W);
            V.push('"m_param":' + Q);
            V.push('"SE":' + JSON.stringify(S))
        } catch (R) {
            V.push('"m_param":null')
        }
        var M = "{" + V.join(",") + "}";
        var O = N.inteneUrl.split("?");
        var L = null;
        if (O.length == 2) {
            L = O[0] + "?" + O[1] + "&params=" + M
        } else {
            L = O[0] + "?params=" + M
        }
        return L
    }

    function y(L) {
        return "intent://m.jd.com/#Intent;scheme=" + L + ";package=com.jingdong.app.mall;end"
    }

    function n(L) {
        if (L.openAppBtnId) {
            B[L.openAppBtnId] = L;
            G(L.openAppBtnId, L.openAppEventId);
            bind(L.openAppBtnId, "click", function() {
                var P = this.getAttribute("id");
                var M = B[P];
                if (!t) {
                    var N = document.createElement("iframe");
                    N.id = iframe;
                    document.body.appendChild(N);
                    document.getElementById(iframe).style.display = "none";
                    document.getElementById(iframe).style.width = "0px";
                    document.getElementById(iframe).style.height = "0px";
                    t = true
                }
                var O = M.cookieFlag ? "downloadAppPlugIn_downCloseDate_" + M.cookieFlag : "downloadAppPlugIn_downCloseDate";
                l(O, Date.now() + "_2592000000", 60, "/", "m.jd.com");
                l(O, Date.now() + "_2592000000", 60, "/", "m.jd.hk");
                openApp(M, true)
            })
        }
    }

    function D(M) {
        if (M.closePanelBtnId && M.closePanelId) {
            B[M.closePanelBtnId] = M;
            G(M.closePanelBtnId, M.closePanelEventId);
            var Q = M.cookieFlag ? "downloadAppPlugIn_downCloseDate_" + M.cookieFlag : "downloadAppPlugIn_downCloseDate";
            var O = e(Q);
            var P = null;
            if (O) {
                P = O.split("_");
                if (P.length == 2) {
                    P[0] = parseInt(P[0], 10);
                    P[1] = parseInt(P[1], 10)
                } else {
                    P = null
                }
            }
            var L = Date.now();
            if (Html5Plus() || (!M.noRecord && P && P.length == 2 && (L - P[0]) < P[1])) {
                document.querySelector("#" + M.closePanelId).style.display = "none";
                if (M.closeCallblack) {
                    var N = M.closeCallblackSource ? M.closeCallblackSource : null;
                    M.closeCallblack.call(N)
                }
                return
            } else {
                document.querySelector("#" + M.closePanelId).style.display = "block"
            }
            bind(M.closePanelBtnId, "click", function() {
                var U = this.getAttribute("id");
                var R = B[U];
                var T = R.cookieFlag ? "downloadAppPlugIn_downCloseDate_" + R.cookieFlag : "downloadAppPlugIn_downCloseDate";
                if (!R.noRecord) {
                    l(T, Date.now() + "_259200000", 60, "/", "m.jd.com");
                    l(T, Date.now() + "_259200000", 60, "/", "m.jd.hk")
                }
                document.querySelector("#" + R.closePanelId).style.display = "none";
                if (R.closeCallblack) {
                    var S = R.closeCallblackSource ? R.closeCallblackSource : null;
                    R.closeCallblack.call(S)
                }
            })
        }
    }

    function Html5Plus() { // htmlplus
        if (Navigator.indexOf("Html5Plus") >= 0) {
            return true
        } else {
            return false
        }
    }

    function G(P, M) {
        try {
            var O = document.getElementById(P);
            var L = O.className;
            if (L) {
                L = L + " J_ping"
            } else {
                L = "J_ping"
            }
            O.className = L;
            O.setAttribute("report-eventid", M)
        } catch (N) {}
    }

    function C(L) {
        var M = F(L);
        n(M);
        D(M)
    }
    Hquery.downloadAppPlugIn = C;
    Hquery.downloadAppPlugInOpenApp = function(L) {
        var M = F(L);
        openApp(M);
    }
});

参考文章: 京东在 html5 页面中打开本地 app 的解决方案 Ios/Android h5 唤起本地 APP iOS/Android 浏览器 (h5) 及微信中唤起本地 APP 极光魔链 mLink JS 集成文档 H5 链接打开原生 App 并跳转到指定界面

作者:前端小学生_f675 链接:https://www.jianshu.com/p/62adf3cac8f2 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
梁君牧 梁君牧
3年前
与你app共享电影资源搜索方法
与你app共享电影资源搜索方法与你app的共享视频是基于云播放技术,支持.mp4和.m3u8后缀名的网址链接播放。app中提供的共享视频过少,并且没有搜索功能。那么如何才能找到自己心仪电影的m3u8格式的网址呢?下面就是教程!Step1.打开网址,改为手机访问模式1.打开此处的网址→2.打开浏览器的调试模式,一般为F12键。点击
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
3年前
Android 8.1实现静默升级踩过的坑
最近在做Android8.1的系统自动升级app的功能,遇到不少坑,特在这里记录下来。首先,我这个自己做的安卓主板,并且安卓系统是经过root过的。如果你的系统没有root,请移步,这篇文章不适合你!先说需求,我的app会自动从服务器上下载最新版的apk安装包,然后自动安装,app监听了android.intent.action.PACKAGE
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Unity横屏
Android下发现Unity里面的Player设置,并不能完全有效,比如打开了自动旋转,启动的时候还是会横屏,修改XML添加以下代码<applicationandroid:icon"@drawable/ic\_launcher"                    android:label"@string/app\_name"
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
邓草
邓草
Lv1
男 · 技术总监
层楼望,春山叠;家何在?
文章
2
粉丝
3
获赞
4