написать впереди
С бычьим рынком в июле все больше и больше людей пополняли ряды А-акций, но риск фондового рынка огромен, некоторые люди разбогатеют в одночасье, а некоторые потеряют все, поэтому для обычных людей фиксированные инвестиции в фонды это хороший выбор Я также маленький лук-порей, в который будет инвестировать фонд.

Когда я иду на работу, я часто чувствую зуд.Я хочу посмотреть, сколько фонд заработал (ge) сегодня.Этапы, чтобы достать мой мобильный телефон и открыть Alipay, слишком громоздки, и я не особо забочусь о других индикаторы.Я просто хочу узнать сегодняшнюю чистую стоимость и увеличить. В качестве инструмента кодирования VS Code предоставляет мощный механизм подключаемых модулей, и мы можем с пользой использовать эту возможность и наблюдать за рынком во время кодирования. Вы можете установить этот плагин, выполнив поиск «fund-watch» в VS Code.

Реализовать плагин
инициализация
VSCode официально предоставляет очень удобный шаблон плагина, мы можем напрямую передатьYeomanдля создания шаблонов для плагинов VS Code.
Сначала установите глобальноyoа такжеgenerator-code, выполните командуyo code.
# 全局安装 yo 模块
npm install -g yo generator-code
Здесь мы используем TypeScript для написания плагинов.


Сгенерированная структура каталогов выглядит следующим образом:

Плагин 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. -
name,publisher:name — это имя плагина, а publisher — издатель.${publisher}.${name}Формирует идентификатор плагина.
Более примечательным являетсяcontributesа такжеactivationEventsэти две конфигурации.
Создать представление
Сначала мы создаем контейнер представления в нашем приложении, контейнер представления представляет собой просто одну боковую панель, вpackage.jsonизcontributes.viewsContainersв конфигурации.
{
"contributes": {
"viewsContainers": {
"activitybar": [
{
"id": "fund-watch",
"title": "FUND WATCH",
"icon": "images/fund.svg"
}
]
}
}
}

Затем нам также нужно добавить представление, вpackage.jsonизcontributes.viewsПоле — это объект, его ключ — это идентификатор нашего контейнера представления, а значение — массив, указывающий, что в контейнер представления можно добавить несколько представлений.
{
"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.

Переключитесь на панель отладки, нажмите «Выполнить напрямую», и вы увидите дополнительный значок на боковой панели.


добавить конфигурацию
Нам нужно получить список фондов и, конечно же, несколько кодов фондов, которые мы можем поместить в конфигурацию 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"
}
]
}
Нам нужно предоставить через vscoderegisterTreeDataProviderПредоставьте данные представлению. открыть сгенерированный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 Codewindow.registerTreeDataProviderДля регистрации данных первый передаваемый параметр представляет идентификатор представления, а второй параметр —TreeDataProviderреализация.
TreeDataProviderНеобходимо реализовать два метода:
-
getChildren: этот метод принимает элемент и возвращает дочерний элемент элемента.Если элемента нет, он возвращает дочерний элемент корневого узла.Поскольку это один список, мы не будем принимать элемент элемента; -
getTreeItem: этот метод принимает элемент и возвращает данные пользовательского интерфейса одной строки представления.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, связанных с фондами, здесь мы используем данные Tiantian Fund.com.

Из запроса видно, что Tiantian Fund Network получает данные, связанные с фондом, через 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: string
name: string
code: string
lastClose: string
changeRate: string
changeAmount: 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 } = info
return 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способ реализации пользовательского интерфейса, теперь нам нужно восстановить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необходимо предоставитьonDidChangeTreeDataAttribute, который является экземпляром EventEmitter, а затем обновляет данные, запуская экземпляр EventEmitter.Каждый вызов метода обновления эквивалентен повторному вызову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: используется для регистрации команды, указывается имя и значок команды, а команда используется для привязки соответствующего события в расширении; -
menus: используется для обозначения позиции, в которой отображается команда;-
when: определите отображаемый вид, конкретный синтаксис можно найти вофициальная документация; - группа: определяет группировку меню;
- команда: определяет событие, вызываемое командой;
-

После настройки команды вернитесь к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() {}
Теперь мы можем обновить вручную.

новый фонд
Мы добавили кнопку для использования «Добавить средства».
{
"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, если вы заинтересованы, вы можете напрямуюскачать плагинили скачать на гитхабеполный код.