写在前面

随着7月一波牛市行情,越来越多的人投身A股行列,但是股市的风险巨大,有人一夜暴富,也有人血本无归,所以对于普通人来说基金定投是个不错的选择,本人也是基金定投的一枚小韭菜。

基金定投

上班的时候经常心理痒痒,想看看今天的基金又赚(ge)了多少钱,拿出手机打开支付宝的步骤过于繁琐,而且我也不太关心其他的指标,只是想知道今天的净值与涨幅。VS Code 做为一个编码工具,提供了强大的插件机制,我们可以好好利用这个能力,可以一边编码的时候一边看看行情。

示例

实现插件

初始化

VSCode 官方提供了非常方便的插件模板,我们可以直接通过 Yeoman 来生成 VS Code 插件的模板。

先全局安装 yo 和 generator-code,运行命令 yo code

# 全局安装 yo 模块
npm install -g yo generator-code

这里我们使用 TypeScript 来编写插件。

yo code

yo code

生成后的目录结构如下:

目录结构

VS Code 插件可以简单理解为一个 Npm 包,也需要一个 package.json 文件,属性与 Npm 包的基本一致。

{// 名称"name": "fund-watch",// 版本"version": "1.0.0",// 描述"description": "实时查看基金行情",// 发布者"publisher": "shenfq",// 版本要求"engines": {"vscode": "^1.45.0"},// 入口文件"main": "./out/extension.js","scripts": {"compile": "tsc -p ./","watch": "tsc -watch -p ./",},"devDependencies": {"@types/node": "^10.14.17","@types/vscode": "^1.41.0","typescript": "^3.9.7"},// 插件配置"contributes": {},// 激活事件"activationEvents": [],
}

简单介绍下其中比较重要的配置。

  • contributes:插件相关配置。

  • activationEvents:激活事件。

  • main:插件的入口文件,与 Npm 包表现一致。

  • namepublisher:name 是插件名,publisher 是发布者。${publisher}.${name} 构成插件 ID。

比较值得关注的就是 contributesactivationEvents 这两个配置。

创建视图

我们首先在我们的应用中创建一个视图容器,视图容器简单来说一个单独的侧边栏,在 package.jsoncontributes.viewsContainers 中进行配置。

{"contributes": {"viewsContainers": {"activitybar": [{"id": "fund-watch","title": "FUND WATCH","icon": "images/fund.svg"}]}}
}

侧边栏

然后我们还需要添加一个视图,在 package.jsoncontributes.views 中进行配置,该字段为一个对象,它的 Key 就是我们视图容器的 id,值为一个数组,表示一个视图容器内可添加多个视图。

{"contributes": {"viewsContainers": {"activitybar": [{"id": "fund-watch","title": "FUND WATCH","icon": "images/fund.svg"}]},"views": {"fund-watch": [{"name": "自选基金","id": "fund-list"}]}}
}

如果你不希望在自定义的视图容器中添加,可以选择 VS Code 自带的视图容器。

  • explorer: 显示在资源管理器侧边栏

  • debug: 显示在调试侧边栏

  • scm: 显示在源代码侧边栏

{"contributes": {"views": {"explorer": [{"name": "自选基金","id": "fund-list"}]}}
}

显示到资源管理器中

运行插件

使用 Yeoman 生成的模板自带 VS Code 运行能力。

vscode配置

切换到调试面板,直接点击运行,就能看到侧边栏多了个图标。

调试面板

运行结果

添加配置

我们需要获取基金的列表,当然需要一些基金代码,而这些代码我们可以放到 VS Code 的配置中。

{"contributes": {// 配置"configuration": {// 配置类型,对象"type": "object",// 配置名称"title": "fund",// 配置的各个属性"properties": {// 自选基金列表"fund.favorites": {// 属性类型"type": "array",// 默认值"default": ["163407","161017"],// 描述"description": "自选基金列表,值为基金代码"},// 刷新时间的间隔"fund.interval": {"type": "number","default": 2,"description": "刷新时间,单位为秒,默认 2 秒"}}}}
}

视图数据

我们回看之前注册的视图,VS Code 中称为树视图。

"views": {"fund-watch": [{"name": "自选基金","id": "fund-list"}]
}

我们需要通过 vscode 提供的 registerTreeDataProvider 为视图提供数据。打开生成的 src/extension.ts 文件,修改代码如下:

// vscode 模块为 VS Code 内置,不需要通过 npm 安装
import { ExtensionContext, commands, window, workspace } from 'vscode';
import Provider from './Provider';// 激活插件
export function activate(context: ExtensionContext) {// 基金类const provider = new Provider();// 数据注册window.registerTreeDataProvider('fund-list', provider);
}export function deactivate() {}

这里我们通过 VS Code 提供的 window.registerTreeDataProvider 来注册数据,传入的第一个参数表示视图 ID,第二个参数是 TreeDataProvider 的实现。

TreeDataProvider 有两个必须实现的方法:

  • getChildren:该方法接受一个 element,返回 element 的子元素,如果没有element,则返回的是根节点的子元素,我们这里因为是单列表,所以不会接受 element 元素;

  • getTreeItem:该方法接受一个 element,返回视图单行的 UI 数据,需要对 TreeItem 进行实例化;

我们通过 VS Code 的资源管理器来展示下这两个方法:

方法展示

有了上面的知识,我们就可以轻松为树视图提供数据了。

import { workspace, TreeDataProvider, TreeItem } from 'vscode';export default class DataProvider implements TreeDataProvider<string> {refresh() {// 更新视图}getTreeItem(element: string): TreeItem {return new TreeItem(element);}getChildren(): string[] {const { order } = this;// 获取配置的基金代码const favorites: string[] = workspace.getConfiguration().get('fund-watch.favorites', []);// 依据代码排序return favorites.sort((prev, next) => (prev >= next ? 1 : -1) * order);}
}

现在运行之后,可能会发现视图上没有数据,这是因为没有配置激活事件。

{"activationEvents": [// 表示 fund-list 视图展示时,激活该插件"onView:fund-list"]
}

基金代码列表

请求数据

我们已经成功将基金代码展示在视图上,接下来就需要请求基金数据了。网上有很多基金相关 api,这里我们使用天天基金网的数据。

天天基金网

通过请求可以看到,天天基金网通过 JSONP 的方式获取基金相关数据,我们只需要构造一个 url,并传入当前时间戳即可。

const url = `https://fundgz.1234567.com.cn/js/${code}.js?rt=${time}`

VS Code 中请求数据,需要使用内部提供的 https 模块,下面我们新建一个 api.ts

import * as https from 'https';// 发起 GET 请求
const request = async (url: string): Promise<string> => {return new Promise((resolve, reject) => {https.get(url, (res) => {let chunks = '';if (!res || res.statusCode !== 200) {reject(new Error('网络请求错误!'));return;}res.on('data', (chunk) => chunks += chunk.toString('utf8'));res.on('end', () => resolve(chunks));});});
};interface FundInfo {now: stringname: stringcode: stringlastClose: stringchangeRate: stringchangeAmount: string
}// 根据基金代码请求基金数据
export default function fundApi(codes: string[]): Promise<FundInfo[]> {const time = Date.now();// 请求列表const promises: Promise<string>[] = codes.map((code) => {const url = `https://fundgz.1234567.com.cn/js/${code}.js?rt=${time}`;return request(url);});return Promise.all(promises).then((results) => {const resultArr: FundInfo[] = [];results.forEach((rsp: string) => {const match = rsp.match(/jsonpgz\((.+)\)/);if (!match || !match[1]) {return;}const str = match[1];const obj = JSON.parse(str);const info: FundInfo = {// 当前净值now: obj.gsz,// 基金名称name: obj.name,// 基金代码code: obj.fundcode,// 昨日净值lastClose: obj.dwjz,// 涨跌幅changeRate: obj.gszzl,// 涨跌额changeAmount: (obj.gsz - obj.dwjz).toFixed(4),};resultArr.push(info);});return resultArr;});
}

接下来修改视图数据。

import { workspace, TreeDataProvider, TreeItem } from 'vscode';
import fundApi from './api';export default class DataProvider implements TreeDataProvider<FundInfo> {// 省略了其他代码getTreeItem(info: FundInfo): TreeItem {// 展示名称和涨跌幅const { name, changeRate } = inforeturn new TreeItem(`${name}  ${changeRate}`);}getChildren(): Promise<FundInfo[]> {const { order } = this;// 获取配置的基金代码const favorites: string[] = workspace.getConfiguration().get('fund-watch.favorites', []);// 获取基金数据return fundApi([...favorites]).then((results: FundInfo[]) => results.sort((prev, next) => (prev.changeRate >= next.changeRate ? 1 : -1) * order));}
}

视图数据

美化格式

前面我们都是通过直接实例化 TreeItem 的方式来实现 UI 的,现在我们需要重新构造一个 TreeItem

import { workspace, TreeDataProvider, TreeItem } from 'vscode';
import FundItem from './TreeItem';
import fundApi from './api';export default class DataProvider implements TreeDataProvider<FundInfo> {// 省略了其他代码getTreeItem(info: FundInfo): FundItem {return new FundItem(info);}
}
// TreeItem
import { TreeItem } from 'vscode';export default class FundItem extends TreeItem {info: FundInfo;constructor(info: FundInfo) {const icon = Number(info.changeRate) >= 0 ? '????' : '????';// 加上 icon,更加直观的知道是涨还是跌super(`${icon}${info.name}   ${info.changeRate}%`);let sliceName = info.name;if (sliceName.length > 8) {sliceName = `${sliceName.slice(0, 8)}...`;}const tips = [`代码: ${info.code}`,`名称: ${sliceName}`,`--------------------------`,`单位净值:    ${info.now}`,`涨跌幅:     ${info.changeRate}%`,`涨跌额:     ${info.changeAmount}`,`昨收:      ${info.lastClose}`,];this.info = info;// tooltip 鼠标悬停时,展示的内容this.tooltip = tips.join('\r\n');}
}

美化后

更新数据

TreeDataProvider 需要提供一个 onDidChangeTreeData 属性,该属性是 EventEmitter 的一个实例,然后通过触发 EventEmitter 实例进行数据的更新,每次调用 refresh 方法相当于重新调用了 getChildren 方法。

import { workspace, Event, EventEmitter, TreeDataProvider } from 'vscode';
import FundItem from './TreeItem';
import fundApi from './api';export default class DataProvider implements TreeDataProvider<FundInfo> {private refreshEvent: EventEmitter<FundInfo | null> = new EventEmitter<FundInfo | null>();readonly onDidChangeTreeData: Event<FundInfo | null> = this.refreshEvent.event;refresh() {// 更新视图setTimeout(() => {this.refreshEvent.fire(null);}, 200);}
}

我们回到 extension.ts,添加一个定时器,让数据定时更新。

import { ExtensionContext, commands, window, workspace } from 'vscode'
import Provider from './data/Provider'// 激活插件
export function activate(context: ExtensionContext) {// 获取 interval 配置let interval = workspace.getConfiguration().get('fund-watch.interval', 2)if (interval < 2) {interval = 2}// 基金类const provider = new Provider()// 数据注册window.registerTreeDataProvider('fund-list', provider)// 定时更新setInterval(() => {provider.refresh()}, interval * 1000)
}export function deactivate() {}

除了定时更新,我们还需要提供手动更新的能力。修改 package.json,注册命令。

{"contributes": {"commands": [{"command": "fund.refresh","title": "刷新","icon": {"light": "images/light/refresh.svg","dark": "images/dark/refresh.svg"}}],"menus": {"view/title": [{"when": "view == fund-list","group": "navigation","command": "fund.refresh"}]}}
}
  • commands:用于注册命令,指定命令的名称、图标,以及 command 用于 extension 中绑定相应事件;

  • menus:用于标记命令展示的位置;

    • when:定义展示的视图,具体语法可以查阅官方文档;

    • group:定义菜单的分组;

    • command:定义命令调用的事件;

view-actions

配置好命令后,回到 extension.ts 中。

import { ExtensionContext, commands, window, workspace } from 'vscode';
import Provider from './Provider';// 激活插件
export function activate(context: ExtensionContext) {let interval = workspace.getConfiguration().get('fund-watch.interval', 2);if (interval < 2) {interval = 2;}// 基金类const provider = new Provider();// 数据注册window.registerTreeDataProvider('fund-list', provider);// 定时任务setInterval(() => {provider.refresh();}, interval * 1000);// 事件context.subscriptions.push(commands.registerCommand('fund.refresh', () => {provider.refresh();}),);
}export function deactivate() {}

现在我们就可以手动刷新了。

image-20200824113219392

新增基金

我们新增一个按钮用了新增基金。

{"contributes": {"commands": [{"command": "fund.add","title": "新增","icon": {"light": "images/light/add.svg","dark": "images/dark/add.svg"}},{"command": "fund.refresh","title": "刷新","icon": {"light": "images/light/refresh.svg","dark": "images/dark/refresh.svg"}}],"menus": {"view/title": [{"command": "fund.add","when": "view == fund-list","group": "navigation"},{"when": "view == fund-list","group": "navigation","command": "fund.refresh"}]}}
}

extension.ts 中注册事件。

import { ExtensionContext, commands, window, workspace } from 'vscode';
import Provider from './Provider';// 激活插件
export function activate(context: ExtensionContext) {// 省略部分代码 ...// 基金类const provider = new Provider();// 事件context.subscriptions.push(commands.registerCommand('fund.add', () => {provider.addFund();}),commands.registerCommand('fund.refresh', () => {provider.refresh();}),);
}export function deactivate() {}

实现新增功能,修改 Provider.ts

import { workspace, Event, EventEmitter, TreeDataProvider } from 'vscode';
import FundItem from './TreeItem';
import fundApi from './api';export default class DataProvider implements TreeDataProvider<FundInfo> {// 省略部分代码 ...// 更新配置updateConfig(funds: string[]) {const config = workspace.getConfiguration();const favorites = Array.from(// 通过 Set 去重new Set([...config.get('fund-watch.favorites', []),...funds,]));config.update('fund-watch.favorites', favorites, true);}async addFund() {// 弹出输入框const res = await window.showInputBox({value: '',valueSelection: [5, -1],prompt: '添加基金到自选',placeHolder: 'Add Fund To Favorite',validateInput: (inputCode: string) => {const codeArray = inputCode.split(/[\W]/);const hasError = codeArray.some((code) => {return code !== '' && !/^\d+$/.test(code);});return hasError ? '基金代码输入有误' : null;},});if (!!res) {const codeArray = res.split(/[\W]/) || [];const result = await fundApi([...codeArray]);if (result && result.length > 0) {// 只更新能正常请求的代码const codes = result.map(i => i.code);this.updateConfig(codes);this.refresh();} else {window.showWarningMessage('stocks not found');}}}
}

新增按钮

输入框

删除基金

最后新增一个按钮,用来删除基金。

{"contributes": {"commands": [{"command": "fund.item.remove","title": "删除"}],"menus": {// 这个按钮放到 context 中"view/item/context": [{"command": "fund.item.remove","when": "view == fund-list","group": "inline"}]}}
}

extension.ts 中注册事件。

import { ExtensionContext, commands, window, workspace } from 'vscode';
import Provider from './Provider';// 激活插件
export function activate(context: ExtensionContext) {// 省略部分代码 ...// 基金类const provider = new Provider();// 事件context.subscriptions.push(commands.registerCommand('fund.add', () => {provider.addFund();}),commands.registerCommand('fund.refresh', () => {provider.refresh();}),commands.registerCommand('fund.item.remove', (fund) => {const { code } = fund;provider.removeConfig(code);provider.refresh();}));
}export function deactivate() {}

实现新增功能,修改 Provider.ts

import { window, workspace, Event, EventEmitter, TreeDataProvider } from 'vscode';
import FundItem from './TreeItem';
import fundApi from './api';export default class DataProvider implements TreeDataProvider<FundInfo> {// 省略部分代码 ...// 删除配置removeConfig(code: string) {const config = workspace.getConfiguration();const favorites: string[] = [...config.get('fund-watch.favorites', [])];const index = favorites.indexOf(code);if (index === -1) {return;}favorites.splice(index, 1);config.update('fund-watch.favorites', favorites, true);}
}

删除按钮

总结

实现过程中也遇到了很多问题,遇到问题可以多翻阅 VSCode 插件中文文档。该插件已经发布的了 VS Code 插件市场,感兴趣的可以直接下载该插件,或者在 github 上下载完整代码。

❤️爱心三连击1.看到这里了就点个在看支持下吧,你的「点赞,在看」是我创作的动力。
2.关注公众号程序员成长指北,回复「1」加入Node进阶交流群!「在这里有好多 Node 开发者,会讨论 Node 知识,互相学习」!
3.也可添加微信【ikoala520】,一起成长。“在看转发”是最大的支持

实战-从零开始实现VS Code基金插件(上班摸鱼可用)相关推荐

  1. idea中摸鱼插件_IDEA插件上班摸鱼神器

    之前看到有网友开发了一款PC端和VS Code插件版的小说阅读器(摸鱼神器Thief-Book),原创链接地址,但是很可惜的是没有IDEA版的,最近刚好比较闲,按照他的原型开发出了类似功能的IDEA插 ...

  2. Pygame实战:程序员小哥给女友写了一款锻炼反应能力的游戏,从此上班摸鱼再也没被扣工资。

    导语 上班摸鱼有没有玩游戏啊! 如果没有 那你也肯定没有玩坠落的小鸟主题游戏咯~ ​ 不过没有关系 木子这就放大图给你过过眼瘾: ​ ​​​ 看看这个界面还真有app游戏软件哪味儿了! 这个可是大人小 ...

  3. GitHub开源了一款程序员摸鱼神器!上班摸鱼还不会被老板发现。。。

    点击上方"Github爱好者社区",选择星标 回复"资料",获取小编整理的一份资料 作者:GG哥 来源:GitHub爱好者社区(github_shequ) 这是 ...

  4. 程序员上班摸鱼,这么玩才高端

    说到上班摸鱼,用手机刷剧.打游戏这些方式都太低端了,不仅低着头对颈椎不好,还容易被老板抓到. 那么,今天就来给大家分享一下高端程序员应该掌握的摸鱼技巧. 1.Ratel 万万没想到,有一天我居然会在命 ...

  5. 张一鸣活捉上班摸鱼员工遭怒怼:不爽退群啊!苏宁质押全部股权给阿里

    " 12 月 9 日字节跳动 CEO 张一鸣发现公司<原神>群中出现一些员工在上班时间非常专注的聊游戏,对此表达了不满.张一鸣称,"虽然公司不禁止上班时间偶尔闲聊,但是 ...

  6. 上班摸鱼,刚刚发现在 VScode 中可玩魂斗罗,超级玛丽

    今天,再给大家介绍一款更加有意思的vscode插件--"小霸王". GitHub传送门:https://github.com/gamedilong/anes-repository ...

  7. 微信重大更新!这特么是为上班摸鱼开发的吧.....(附内测地址)

    来自:民工哥技术之路 近日,腾讯微信PC版迎来了2.9.0.测试版.相信很多人都会爱上这个功能!毕竟我怀疑是为上班摸鱼的那些小伙伴的开发的.... 此次更新增添了不少新功能.最引人注目的也是我期待已久 ...

  8. 张一鸣活捉上班摸鱼员工遭怒怼:不爽退群啊!

    12 月 9 日字节跳动 CEO 张一鸣发现公司<原神>群中出现一些员工在上班时间非常专注的聊游戏,对此表达了不满.张一鸣称,"虽然公司不禁止上班时间偶尔闲聊,但是连续几天都在游 ...

  9. 这4个在线游戏网站,上班摸鱼必备

    1.io游戏站        io游戏一般是指一些多人大乱斗游戏,比如球球大作战,蛇蛇大作战等,这些游戏的特点就是多人同一地图,竞技PK,游戏时间短,死亡后重新发育继续作战,非常适合上班摸鱼. 但是这 ...

最新文章

  1. HTTP简介、请求方法与响应状态码
  2. mac os vmware 显卡驱动_【新机】华为Mate 40系列国行售价明天公布,饿了么可以买手机?| 干翻牙膏厂,AMD发布RX6000显卡...
  3. Async Await
  4. unicode字符大全可复制_说说Excel不可见字符的那些事
  5. 明年5G智能手机大爆发!出货量惊人
  6. 刘海I关于iPhone X 的适配
  7. c 调用 android jar包,Unity调用AndroidStudio导出的Jar包
  8. 项目管理十大过程思维导图
  9. 解决hive表中comment中文乱码问题
  10. 漫画 | 阿姨,我不想努力了~
  11. 利用数据库在java实现已读未读消息公告
  12. 听说你在做斗鱼APP?
  13. python实现食品推荐_通过Python语言实现美团美食商家数据抓取
  14. 如何将png图片转为heif格式
  15. 倒计时1天!亮点抢先看,2022京东云产业融合新品发布会
  16. 2022年的第一篇程序人生。。。
  17. R语言学习——安装R语言,安装RStudio
  18. Python 字符串格式化 f-string f“{}“ .format
  19. 第七十三篇:从ADAS到自动驾驶(六):可行驶区域检测
  20. c#加密证件号的中间部分,改为*号

热门文章

  1. FreeSWITCH SRTP测试
  2. arm 处理器的堆栈操作
  3. html怎么消除文字锯齿,html – 如何在网页中做字体抗锯齿?
  4. 包教包会——Cookie、Session、Token、JWT
  5. c#+sql语言开发的小区物业管理系统,基于C#环境下的物业管理系统.doc
  6. Redis went away
  7. Java实现CD出租销售商店
  8. osg_操作器、碰撞检测、上楼梯篇
  9. 【国旗迷】世界各国国旗国徽国歌大全 flags.lantianye3.top
  10. MySQL 5.7 have_ssl 的SSL加密方法