如果生活有奇迹,那一定是努力的轨迹!
Hello,大家好,我是小羽同学,一个平凡而又不甘于平凡的前端开发工程师~
不知道小伙伴们有没有用过Octotree
这个插件呢?
没用过也没关系。
因为github
是在国外的,所以延迟
还算是比较高的,当我们需要找一个隐藏的比较深
的文件时,可能就需要花费不少时间。而Octotree
这个插件主要是为我们的github提供了一个树形的目录结构
,进来就加载了整个项目的目录,这样就可以快速
找到我们所需要的的文件,节省
了由于延迟需要的时间
。
那小伙伴们有没有兴趣快速的撸一个自己的chrome插件
,给掘金网站添加一些自己喜欢的功能
呢?
来,下面小羽
就带着大家撸一个可作用于掘金网站上的chrome插件。
1.创建chrome插件项目
这里的话小羽直接使用自己搭建的一个chrome插件脚手架
进行项目的创建,该脚手架是基于vue3.x
进行搭建的,详情可以点击这里去查看。
全局安装sulg-plugin-cli
npm install sulg-plugin-cli -g
复制代码
查看sulg-plugin-cli版本,出现如下图所示,说明脚手架安装成功
sulgPlugin -v
复制代码
创建项目
sulgPlugin create douban-movie-plugin
复制代码
选择模板类型,包含了三个模板
default是不带任何ui框架
antd2.0则是基于ant-design-vue2.0框架的模板
elementPlus则为官方elementui的vue3版本
这里选择了elementPlus
然后选择模板的下载地址,可以选择github或者gitee,国内的小伙伴建议选择gitee,速度会更快一些 。
接着就是填入一些项目的相关信息,然后就会自动下载好我们的模板。
2.启动项目
2.1安装modules
先安装node_modules
yarn install
复制代码
启动命令有两个,一个是用来编辑插件的,一个用来编辑插件介绍页面的
让小羽来逐个说明。
2.2编辑插件命令
yarn watch
复制代码
这个就是编辑插件命令,它主要就是把我们的插件编译并拷贝到dist文件夹中(如下图),然后热更新。
打开我们的chrome浏览器,加载我们的刚刚的插件。
然后打开百度的网址,就可以发现在右下角多出了一个弹窗
2.3编辑插件介绍页命令
yarn serve
复制代码
该命令主要是用来撰写插件介绍页面的。注意这里的下载是暂时无法下载
的,因为小羽写的初始化模板中的下载,需要结合dockerFile
构建镜像,然后在通过docker部署服务
,才可以下载的。
3.编写插件
3.1 插件结构介绍
sulgPlugin创建的模板大体结构大概如下图所示。
mainfest.json
是一个Chrome插件最重要也是必不可少的文件,用来配置所有和插件相关的配置,必须放在根目录。其中,manifest_version
、name
、version
3个是必不可少的,description
和icons
是推荐的。content部分
是chrome插件中可以向页面中注入
的一种形式。background部分
是chrome拆件的常驻模块
,随着浏览器的打开而打开,随着浏览器的关闭而关闭,所以通常把需要一直运行的、启动就运行的、全局的代码放在这里。popup部分
是点击插件icon后出现的一个小弹窗
,生命周期就是弹窗显示的时间。
3.2 修改插件配置
首先修改我们的插件使用网站
,这里小羽修改为掘金
的网址,然后打开掘金的页面,发现我们的插件已经可以在掘金的右下方显示一个小弹窗了。并且这时候我们重新打开一个百度的页面,在右下方也不会有弹窗的显示了。
路径:src/plugins/manifest.json
ps:如果要匹配所有的地址,matches的值为
["<all_urls>"]
接着修改咱们插件的相关描述信息
,然后移动鼠标到插件图标
上/打开chrome的扩展程序面板
,可以发现发现我们的插件描述信息
已经发生了改变。
如果小伙伴们想修改icon
的话,修改这两处即可。
3.3 调试api接口
删除旧的test.js文件,然后新增weather.js。
这里的话,用到了百度
和中国天气网
的api接口。
import axios from "axios";
class Weather {
// 获取地理坐标
async getAreaLocation(){
return await axios.get(`https://map.baidu.com/?qt=ipLocation&t=${new Date().getTime()}`).then(res=>{
if(!res.data.rgc || !res.data.rgc.result.location){
// 如果定位失败则返回广州的地理位置
return {
lat: 23.188855624765,
lng: 113.45977672508
}
}
return res.data.rgc.result.location
})
}
// 获取id
async getAreaId(lat,lng){
let params = {"method":"stationinfo",lat,lng}
let res = await axios.get(`https://d4.weather.com.cn/geong/v1/api?params=${encodeURI(JSON.stringify(params))}`).then(res=>res.data.data.station.areaid)
return res
}
// 获取天气信息
async getWeather(areaId) {
console.log("areaId",areaId)
let res1 = await axios.get(`https://d1.weather.com.cn/wap40/${areaId}.html?_=${new Date().getTime()}`).then(res => {
return JSON.parse(res.data.split('=')[1])
})
let res2 = await axios.get(`https://d1.weather.com.cn/sk_2d/${areaId}.html?_=${new Date().getTime()}`).then(res => {
return JSON.parse(res.data.split('=')[1])
})
return {
list: res1,
current: res2,
}
}
}
const weather = new Weather()
export default weather;
复制代码
修改content/components/app.vue,所有演示文件直接删除
,修改为如下的测试代码,保存后测试一下api接口是否正常。
<!--
* @Description:
* @Author: 小羽
* @LastEditors: 小羽
* @Date: 2021-06-15 23:09:55
* @LastEditTime: 2021-06-15 23:15:18
-->
<template>
<div class="content-page">
<el-button @click="apiTest">api测试</el-button>
</div>
</template>
<script>
export default {
methods:{
apiTest(){
this.$api.weatherApi.getAreaLocation()
}
}
}
</script>
<style lang="less" scoped>
.content-page {
text-align: left;
box-shadow: 0 0 5px #999;
background: #fff;
padding: 10px;
//color: red;
position: fixed;
z-index: 100001;
right: 0;
bottom: 0;
}
</style>
复制代码
嗯,咱们的api测试按钮的确可以正常显示了,但是api请求跨域
了!!!
跨域而已嘛,简单。
常见方法——cors:
我:后端大佬,你看方便帮忙加个
cors
头不?后端大佬:哦哦,这个简单,要加那个接口?
我:就是那个百度的api接口,很简单吧,嘿嘿,
两分钟
能搞定了吧?后端大佬:你说啥?百度的api接口,你信不信老子给你一锤?
常见方法——代理:
得,后端大佬帮不了忙,那就自己来写代理呗,多大点事。
nginx
可以做代理,node
貌似也ok。那就整起来。
咳咳,兄弟,你是不是忘了一件事情。
掘金的nginx服务器在你手中吗?
你问过
优弧
大佬的意见没?整天就在瞎搞!!!
哦豁,没得玩了,散场
,小伙伴们下次见。。。
嘿嘿,开个玩笑,你们不会以为小羽是这么容易就放弃的人吧?【惊恐】
经过查阅资料后发现,background
中的权限非常大。
大到什么程度呢?
可以直接无视
浏览器的跨域
问题。
那咱们继续走起。
修改manitest.json
中的permissions
修改src/background/main.js,然后在chrome扩展程序中打开咱们插件的background
控制台。
可以发现没有报跨域
的错误了,完美~
/*
* @description:
* @author: 小羽
* @Date: 2021-01-12 18:39:58
* @LastEditTime: 2021-06-16 00:39:30
* @Copyright: 1.0.0
*/
import hotReload from '@/utils/hotReload'
import api from "@/api/index.js"
import { getCurrentTabId } from "../utils/chrome"
hotReload()
console.log('this is background main.js')
api.weatherApi.getAreaLocation().then(res=>{
console.log("api-test",res)
})
// chrome长连接消息发送
async function longConnectMsgSend(type,data){
let connectObj = {
content:"sulg-long-connect-content",
popup:"sulg-long-connect-popup"
}
let tabId = await getCurrentTabId()
let port = chrome.tabs.connect(tabId, {name: connectObj[type]});
port.postMessage(data)
}
复制代码
3.4 content与background的通信
在这之前咱们先来学习两个知识点。
在chrome中有两种通信方式,短连接和长连接。
短链接(chrome.tabs.sendMessage
和chrome.runtime.sendMessage
)的时间非常短,而且不会对async/await进行等待,只要断开了就需要重新发送。
而长连接(chrome.tabs.connect
和chrome.runtime.connect
)的话,则可以进行async/await等操作。
小羽
在脚手架生成的项目中,已经对短链接
和长连接
做了一些简单的封装
。直接使用即可。
修改src/content/components/app.vue
<!--
* @Description:
* @Author: 小羽
* @LastEditors: 小羽
* @Date: 2021-06-15 23:09:55
* @LastEditTime: 2021-06-16 01:01:04
-->
<template>
<div class="content-page">
<el-button @click="apiTest">api测试</el-button>
</div>
</template>
<script>
import { sortConnectMsgSend } from "../../utils/chrome";
export default {
methods:{
async apiTest(){
let res = await sortConnectMsgSend({ name: "getWeatherData",data:{}})
console.log("收到短连接消息",res)
chrome.runtime.onConnect.addListener(port=>{
if(port.name == 'sulg-long-connect-content') {
port.onMessage.addListener(msg => {
console.log('收到长连接消息:', msg);
});
}
});
}
}
}
</script>
<style lang="less" scoped>
.content-page {
text-align: left;
box-shadow: 0 0 5px #999;
background: #fff;
padding: 10px;
//color: red;
position: fixed;
z-index: 100001;
right: 0;
bottom: 0;
}
</style>
复制代码
修改src/backgournd/main.js
/*
* @description:
* @author: 小羽
* @Date: 2021-01-12 18:39:58
* @LastEditTime: 2021-06-16 01:03:16
* @Copyright: 1.0.0
*/
import hotReload from '@/utils/hotReload'
import api from "@/api/index.js"
import { getCurrentTabId } from "../utils/chrome"
hotReload()
console.log('this is background main.js')
// 监听来自content-script的消息
chrome.runtime.onMessage.addListener(async function (request, sender, sendResponse) {
console.log("收到content发来的短连接消息")
sendResponse(sender)
let requectFunc = {
getWeatherData:async data =>{
let areaLocation = await api.weatherApi.getAreaLocation()
let areaId = await api.weatherApi.getAreaId(areaLocation.lat,areaLocation.lng)
let weatherData = await api.weatherApi.getWeather(areaId)
longConnectMsgSend('content',{name:'getWeatherData',weatherData})
}
}
requectFunc[request.name] && requectFunc[request.name](request.data)
});
// chrome长连接消息发送
async function longConnectMsgSend(type,data){
let connectObj = {
content:"sulg-long-connect-content",
popup:"sulg-long-connect-popup"
}
let tabId = await getCurrentTabId()
let port = chrome.tabs.connect(tabId, {name: connectObj[type]});
port.postMessage(data)
}
复制代码
emmm,可以看到我们的数据已经可以正常返回到前端了,剩下来的就是渲染页面
啦。
3.5 完成插件
其实到上一小结其实整个流程
就已经基本完成
插件的编写,剩下的就是简单的将数据渲染
出来。
小羽还没怎么学vue3
,所以还是使用vue2
的options api
的方式(vue3也是兼容
options api的)
修改src/content/components/app.vue
<!--
* @description:
* @author: 小羽
* @Date: 2021-01-12 18:40:31
* @LastEditTime: 2021-06-16 00:55:38
* @Copyright: 1.0.0
-->
<template>
<div class="content-page">
<div class="content-page-main" v-if="weatherData.current">
<div class="content-page-main-left">
<div class="weather-temp">
<span class="weather-temp-num">{{weatherData.current.temp}}</span>
<span class="weather-temp-symbol">°C</span>
<span class="weather-temp-type">{{weatherData.current.weather}}</span>
</div>
<div class="weather-other">
<div class="weather-other-item">湿度:{{weatherData.current.SD}} |</div>
<div class="weather-other-item">PM2.5:{{weatherData.current.aqi_pm25}}</div>
</div>
</div>
<div class="content-page-main-right">
<div>{{weatherData.current.cityname}} {{weatherData.current.date}}</div>
<div>更新时间: {{weatherData.current.time}}</div>
<div>{{weatherData.current.WD}}</div>
</div>
</div>
<div ref="myChart" class="chart-box"></div>
</div>
</template>
<script>
import { sortConnectMsgSend } from "../../utils/chrome";
export default {
data() {
return {
//weatherData:{}
weatherData:{
}
};
},
async mounted() {
await this._initData();
},
methods: {
/**
* @description: 数据初始化
* @Date: 2021-01-14 13:31:30
* @author: 小羽
* @param {*}
* @return {*}
*/
async _initData() {
sortConnectMsgSend({ name: "getWeatherData",data:{}})
chrome.runtime.onConnect.addListener(port=>{
if(port.name == 'sulg-long-connect-content') {
port.onMessage.addListener(msg => {
console.log('收到长连接消息:', msg);
if(msg.name === "getWeatherData"){
this.weatherData = msg.weatherData
console.log(this.weatherData.current)
this.drawEchart()
}
});
}
});
},
drawEchart(){
let timeList = [],hightTemp = [],lowTemp = []
for(let i = 0;i<7;i++){
let current = this.weatherData.list.datas[i]
timeList.push(current.tm.match(/(\d{4})(\d{2})(\d{2})/).filter((item,index) => index > 0).join('-'))
hightTemp.push(current.max)
lowTemp.push(current.min)
}
let options = {
tooltip: {
trigger: 'axis'
},
color:["#ffdc51","#9dc0f7"],
grid:{
bottom:10,
right:35,
containLabel:true
},
legend: {
data: ['最高气温', '最低气温']
},
xAxis: {
type: 'category',
boundaryGap: false,
data: timeList
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value} °C'
}
},
series: [
{
name: '最高气温',
type: 'line',
data: hightTemp,
markPoint: {
data: [
{type: 'max', name: '最大值'},
]
}
},
{
name: '最低气温',
type: 'line',
data: lowTemp,
markPoint: {
data: [
{type: 'min', name: '最小值'}
]
},
}
]
};
this.$nextTick(()=>{
let myChart = this.$echarts.init(this.$refs.myChart);
myChart.setOption(options)
})
}
},
};
</script>
<style lang="less" scoped>
.content-page {
text-align: left;
background: #1e77e9;
color: #fff;
box-shadow: 0 0 5px #999;
padding: 10px;
//color: red;
position: fixed;
z-index: 100001;
right: 0;
bottom: 0;
&-main {
display: flex;
margin-bottom: 10px;
&-left{
width: 50%;
.weather-temp{
display: flex;
align-items: flex-start;
&-num{
font-size: 32px;
font-weight: bold;
}
&-type{
margin-left: 5px;
}
}
.weather-other{
display: flex;
align-items: center;
&-item{
margin-right: 5px;
}
}
}
&-right{
width: 50%;
text-align: right;
}
}
.chart-box{
width: 300px;
height: 200px;
background: #fff;
border-radius: 5px;
padding: 10px;
}
}
</style>
复制代码
修改src/popup/components/app.vue
<template>
<div class="popup_page">这是一个chrome天气插件</div>
</template>
<script>
export default {
name: "popup",
mounted() {
},
methods: {
},
};
</script>
<style lang="less" scoped>
.popup_page {
width: 200px;
color: #fff;
background: #1e77e9;
padding: 10px;
text-align: center;
}
</style>
复制代码
最终结果演示
:
4.编写插件介绍页面
这里的话小羽就简单的写下介绍页面
。这个页面当初主要是设计
用来介绍
chrome插件的用法的,剩下的小伙伴们自己发挥吧~
修改src/index/components/app.vue
<!--
* @Description:
* @Author: 小羽
* @LastEditors: 小羽
* @Date: 2021-06-13 15:29:37
* @LastEditTime: 2021-06-24 00:16:12
-->
<template>
<div class="index-page">
<div class="index-page-main">
<h1 class="chrome-title">掘金天气插件</h1>
<div class="chrome-description">这是一个chrome天气插件,可作用于掘金网站。</div>
<el-link href="/weather-plugin.zip" type="primary" target="_blank">下载</el-link>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.index-page{
display: flex;
justify-content: center;
&-main{
text-align: center;
width: 300px;
border: solid 1px #eee;
padding: 0 30px 30px 30px;
.chrome{
&-title{}
&-description{
text-indent: 2em;
text-align: left;
}
}
}
}
</style>
复制代码
启动
yarn serve
复制代码
打开http://localhost:8080 ,就可以看到咱们的介绍页面就写好了
5.关于部署
5.1 docker容器部署
在sulgPlugin脚手架
的elementPlus
和antdv
模板中默认配置了dockerFile
。
所以咱们可以通过dockerFile
结合jenkins
完成流水线(CI/CD)
的一键部署,但是由于篇幅过长,在这里暂时就不说了。
咱们就简单的说下在win中配合docker desktop简单的完成部署。
对docker
不感兴趣的同学可以跳到下一小节了~
下载安装docker desktop
关于docker 的用法可以看这里
通过docker build
命令,构建docker镜像,等待构建完成,然后打开docker desktop,可以看到咱们刚刚构建好的镜像。
docker build -t xiaoyu/weather-plugin:v1 .
复制代码
然后可以点击run按钮,可视化启动docker。也可以通过docker run命令启动docker
docker run -itd --name weather -p 35256:80 xiaoyu/weather-plugin:v1
复制代码
然后点击打开页面的按钮,或者在浏览器中直接输入http://localhost:35256 ,即可看到咱们编写的插件介绍页面,点击下载后也是可以下载咱们的插件。
关于怎么加载插件
?
大兄弟,请看2.2小节
。
5.2 传统服务器部署
当然除了dockerFile配合jenkins完成流水线的部署外咱们也是可以使用传统
的方式部署。
通过
yarn build
打包,然后把dist中的文件压缩成zip文件,并重命名weather-plugin.zip
把index.html和需要的文件
拷贝
到nginx指向的路径中(小羽写的dockerFile中是没有分的,直接把整个dist文件拷走,反正也不大,关于需要的文件可以看下下图)把weather-plugin.zip
拷贝
到对应的路径中
重启nginx
,完成部署
参考文献
后语
本文小羽和小伙伴们基于sulgPlugin脚手架
,结合vue3+axios
完成了一个可作用于掘金
网站上的chrome天气插件
。
不过,有点小小的遗憾
,就是小羽最近刚刚转react
,没怎么学vue3
的composition API
,就没有献丑了。还是使用原来的options API
的方式。
如果看这篇文章后,感觉有收获的小伙伴们可以点赞
+关注
哦~
如果想和小羽
交流技术可以加下wx,也欢迎小伙伴们来和小羽唠唠嗑
,嘿嘿~
ps:本文原创,转载请标明出处
最后奉上chrome天气插件
的相关连接
官网链接:weather.sulg.top/
GitHub链接:github.com/sulgweb/wea…
如果条件允许的话,希望小伙伴们给小羽的github添加一颗小星星哦~