做有态度的前端团队

网易FEG前端团队

写了一个Chrome扩展,方便查看NBA赛况

前言

最近写了个Chrome扩展,在这里和大家分享一下。

我们小组日常工作用的检查工具就是一个Chrome扩展程序,可以看看这篇文章,里面有一些介绍:chrome工具优化的学习小结...当然我写的这个比较简单,没那么多复杂的功能。这个其实和我们平常写web页面差不多,所以我们前端可以写,于是我也来试试这个东西~

先来看看效果图:

默认展示当天的赛况
25.png

还有相邻两天的赛况

26.png
28.png

目录结构

├── css
│   ├── index.css
│   ├── boostrap.min.css
├── img
│   ├── icon-128.png
│   ├── icon-48.png
│   └── icon.png
├── js
│   ├── lib
│   │    └── jqurey.min.js  
│   │    └── boostrap.min.js        
│   └── index.js
├── manifest.json
└── popup.html

manifest.json

入口文件,每个Chrome插件都必须包含一个manifest.json文件,其中必须包含nameversionmanifest_version属性

{  
  "name": "NBA Plugin",  
  "version": "1.0",  
  "description": "A Chrome extension for checking nba match",  
  "browser_action": {  
    "default_icon": "img/icon.png" ,
    "default_title": "NBA",
    "default_popup": "popup.html"
  },
  "icons": {
      "16": "img/icon.png",
      "48": "img/icon-48.png",
      "128": "img/icon-128.png"
  },
  "permissions": [
      "http://*/*",
      "https://*/*"
  ],
  "manifest_version": 2  
} 

属性说明:

  • manifest_version:此键指定此扩展使用的manifest.json的版本,目前必须是2
  • version:插件版本号
  • name:插件名称
  • description:插件描述
  • icons:插件图标,Chrome扩展程序页显示
  • browser_action:指定插件在Chrome工具栏中的显示信息
    1. default_icon:图标
    2. default_title:标题
    3. default_popup:弹出页
  • permissions:权限
    注意:如果我们需要向服务器请求数据,就需要在permissions中添加请求数据的接口,否则会报跨域请求的限制。但是如果需要向多个接口请求数据,建议直接按我的方式书写匹配规则,这样不管多少接口都适用。

此外,还有其他的一些配置,暂时没用到。

popup.html

这个就是安装扩展之后,点击浏览器右上角小icon出现的弹出页。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>NBA</title>
<meta name="description" content="">
<meta name="keywords" content="">
<link href="css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="css/index.css">
</head>
<body>

    <div class="container">
        
        <ul id="myTab" class="nav nav-tabs">
            <li><a href="#content1" class="tab yesterday" data-toggle="tab"></a></li>
            <li class="active"><a href="#content2" class="tab today" data-toggle="tab"></a></li>
            <li><a href="#content3" class="tab tomorrow"  data-toggle="tab"></a></li>
        </ul>
        <div id="myTabContent" class="tab-content">
            <div class="tab-pane fade" id="content1">
                <table>
                </table>
            </div>
            <div class="tab-pane fade in active" id="content2">
                <table>
                </table>
            </div>
            <div class="tab-pane fade" id="content3">
                <table>
                </table>
            </div>
        </div>
        <div class="bottomlink">
            <div class="left"></div>
            <div class="right dropup">
                  <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">
                    按球队查看赛程
                    <span class="caret"></span>
                  </button>
                  <ul class="dropdown-menu"></ul>
            </div>
        </div>
    </div>
    <script type="text/javascript" src="js/lib/jquery.min.js"></script>
    <script type="text/javascript" src="js/lib/bootstrap.min.js"></script>
    <script type="text/javascript" src="js/index.js"></script>

</body>
</html>

index.js

我在这里拿到了一个NBA数据的接口:http://op.juhe.cn/onebox/basketball/nba?key=c763e527b86addb9e21d455e4c467879
,其中这个KEY需要申请一下。PS:这个接口有个别字段还有点问题,url不对,比如点湖人队,查看赛程,跳去了http://kbs.sports.qq.com/kbsweb/teams.htm?tid=13,其实正确应该是http://kbs.sports.qq.com/kbsweb/teams.htm?tid=13&cid=100000


class NbaMatch {

    static getData() {
        $.ajax({
            type: "GET",
            url: "http://op.juhe.cn/onebox/basketball/nba?key=c763e527b86addb9e21d455e4c467879",
            success: function(data) {

                let result = data.result;
                let listHtml = [];
                let arr = [];
                let dataList = result.list;


                for (let i = 0, len = dataList.length; i < len; i++) {

                    $('.tab').eq(i).html(dataList[i].title)

                    for (let j = 0, len = dataList[i].tr.length; j < len; j++) {

                        arr[i] = dataList[i].tr[j];

                        let status = arr[i].status; //比赛状态:0未开赛、1直播中、2已结束
                        let statusText;

                        switch (status) {
                            case '0':
                                statusText = '未开赛';
                                break;
                            case '1':
                                statusText = '直播中';
                                break;
                            case '2':
                                statusText = '已结束';
                                break;
                            default:
                                statusText = '未知';
                        }

                        //每天的比赛列表分别存入数组
                        listHtml[i] = `<tr>
                                        <td class="status${status}">${statusText}</td>
                                        <td>${arr[i].time}</td>
                                        <td width="64"><a href="${arr[i].player1url}" target='_blank'>${arr[i].player1}</a></td>
                                        <td><img src="${arr[i].player1logo}" alt="logo" /></td>
                                        <td>${arr[i].score}</td>
                                        <td><img src="${arr[i].player2logo}" alt="logo" /></td>
                                        <td width="64"><a href="${arr[i].player2url}" target='_blank'>${arr[i].player2}</td>
                                        <td><a href="${arr[i].link1url}" target="_blank">${arr[i].link1text}</a></td>
                                        <td class="ds"><a href="${arr[i].link2url}" target="_blank">${arr[i].link2text}</a></td>
                                    </tr>`;

                        $('table').eq(i).append(listHtml[i]);
                    }
                }

                $('.ds a').each(function() {
                        $(this).click(() => {
                            let a = $(this).attr('href');
                            if (a == '' || a == 'javascript:;') {
                                $(this).attr('href', 'javascript:;')
                                alert('暂无数据');
                            }
                        })
                })
                    // 常规赛赛程,球队排名,球员排名,社区讨论
                let bottomlink = dataList[0].bottomlink;
                let blHtml = '';
                for (let i = 0, len = bottomlink.length; i < len; i++) {
                    blHtml += `<a href="${bottomlink[i].url}" target="_blank">${bottomlink[i].text}</a>`
                }
                $('.bottomlink .left').append(blHtml);
                
                //球队下拉列表
                let teammatch = result.teammatch;
                let tmHtml = '';
                for (let i = 0, len = teammatch.length; i < len; i++) {
                    tmHtml += `<li><a href="${teammatch[i].url}" target="_blank">
                              ${teammatch[i].name}</a></li>`;
                }
                $('.dropdown-menu').append(tmHtml);

            }
        })
    }
}

NbaMatch.getData();

安装本扩展

我把这个放在GitHub上了,有兴趣的可以体验一下。这里说下如何安装:

在Chrome浏览器地址栏打开chrome://extensions/ ,(其他浏览器就不是这个地址了,如QQ浏览器,360浏览器之类的,反正找到安装扩展的地方即可)

15.png

把这个文件夹拖拽到上面

41.png

然后就OK了。

可能有个别浏览器不能这样拖拽安装,还可以点这里安装的。

7.png

遇到的问题

1、在开发的过程中,可能会用到Chrome的一些API(不过我这里的这个小应用还暂时用不上),比如你想获取当前页面的url,像平常这样写window.location.href是获取不到的,需要这样写:

chrome.tabs.query({active:true, currentWindow:true}, function(tab){
      var curUrl = tab[0].url; //获取当前页面的url
      //dosomething
});

然后还需要在manifest.json配置文件中声明 "tabs" 权限

  "permissions": [
      "tabs"
  ]

2、引用资源文件不能直接用外链,要写相对路径,引用项目目录下的资源。这个是出于安全的考虑,不允许那样做。当然,它还是给我们留了后路。举个例子:如果想引用这个外部脚本

<script type="text/javascript" src="https://cdn.staticfile.org/jquery/3.1.1/jquery.min.js"></script>

可以在manifest.json文件这样配置一下:

"content_security_policy": "script-src 'self' https://cdn.staticfile.org; object-src 'self'"

这个也仅限于HTTPS的资源,通过将HTTPS源的脚本加入白名单来放宽“只加载本地脚本”的策略。

3、unsafe-inline报错:

Refused to execute JavaScript URL because it violates the following Content
 Security Policy directive: "script-src 'self' blob: filesystem: chrome-extension-resource:". 
 Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to
  enable inline execution.

虽然我知道写inline javascript会报这个错,就是说在html内不能像这样加js,比如这样:

<button onclick=""></button>

解决方法也简单,js不要写在HTML内,写在js文件呗。但我没想到在动态添加的Dom节点上绑定事件后也出现了这个问题,比如我这样写

$('.ds a').each(function() {
    $(this).click(() => {
        let a = $(this).attr('href');
        if (a == '' || a == 'javascript:;') {
            $(this).attr('href', 'javascript:;')
            alert('未开赛,暂无数据');
        }
    })
})

我尝试过去掉click事件,只是改变属性,然后点击之后还是会报这个错。暂时没找到解决方法...

4、如果你是用jQuery的Ajax方法获取非本域的数据,不要加上dataType:"jsonp",否则会报错。jsonp并不是通过XMLHttpRequest实现的Ajax请求的,它是通过script标签实现跨域的,那么这样src填的就是绝对路径了,上面第2点说了一般是不可以直接引用外部资源的。其实manifest.json文件已经声明了permissions权限,获取了跨域请求许可,已经可以进行跨域请求的了,所以没必要写上dataType:"jsonp"

5、Chrome浏览器安装之后,会出现这个问题,然后每次启动后都要点一下取消,非常烦人。

8.png

这个也是Chrome的一个保护机制,提醒用户这样安装有风险,扩展应该来源于Chrome Web Store。但是有些时候我们开发的工具只是个人使用,就没发布到商店去。想解决掉这个的问题,还是有办法的。

简单地说就是在这里拿到这个ID(注意这个ID不是固定不变的) ,然后添加扩展程序白名单。网上有很多文章有介绍,这里就不描述了。比如这篇:屏蔽Google Chrome安装第三方插件之后反复提示“请停用以开发者模式运行的扩展程序”

9.png

我下面补充一点,是关于安装方式的,由于这个原因chrome浏览器下就不能按照上面那种方式安装了。要点打包扩展程序这里:

10.png

然后在和扩展程序文件夹同级目录下会生成一个crx和pem文件。

11.png

把生成的crx文件拖拽安装,然后到了这一步:

12.png
13.png

可能第一次还要点一下“已启用”,到这如无意外就正常了。

14.png

手机阅读请扫描下方二维码: