diff --git a/package.json b/package.json index e6d11f03..c6e28734 100644 --- a/package.json +++ b/package.json @@ -171,6 +171,19 @@ "title": "添加股票", "icon": "$(add)" }, + { + "command": "leek-fund.addStockGroup", + "title": "添加股票分组", + "icon": "$(diff-added)" + }, + { + "command": "leek-fund.renameStockGroup", + "title": "修改分组名称" + }, + { + "command": "leek-fund.removeStockGroup", + "title": "删除分组" + }, { "command": "leek-fund.setStockPrice", "title": "设置股票成本价", @@ -333,7 +346,7 @@ "group": "navigation" }, { - "command": "leek-fund.addStock", + "command": "leek-fund.addStockGroup", "when": "view == leekFundView.stock", "group": "navigation" }, @@ -409,6 +422,11 @@ "when": "view == leekFundView.fund && viewItem == category", "group": "inline" }, + { + "command": "leek-fund.addStock", + "when": "view == leekFundView.stock && viewItem == category", + "group": "inline" + }, { "command": "leek-fund.deleteFund", "when": "view == leekFundView.fund && viewItem != category", @@ -424,6 +442,16 @@ "when": "view == leekFundView.fund && viewItem == category", "group": "group7" }, + { + "command": "leek-fund.removeStockGroup", + "when": "view == leekFundView.stock && viewItem == category", + "group": "group8" + }, + { + "command": "leek-fund.renameStockGroup", + "when": "view == leekFundView.stock && viewItem == category", + "group": "group8" + }, { "command": "leek-fund.setStockTop", "when": "view == leekFundView.stock && viewItem != category && viewItem!=nodata", @@ -519,6 +547,13 @@ ], "description": "配置需要监控的股票代码(建议通过界面新增)" }, + "leek-fund.stockGroups": { + "type": "array", + "default": [ + "My Stocks" + ], + "description": "配置股票分组名称(建议通过界面新增)" + }, "leek-fund.stockPrice": { "type": "object", "default": {}, diff --git a/src/explorer/stockProvider.ts b/src/explorer/stockProvider.ts index 81bfb810..a3e1e90b 100644 --- a/src/explorer/stockProvider.ts +++ b/src/explorer/stockProvider.ts @@ -34,35 +34,19 @@ export class StockProvider implements TreeDataProvider { getChildren(element?: LeekTreeItem | undefined): LeekTreeItem[] | Thenable { if (!element) { - // Root view - const stockCodes = LeekFundConfig.getConfig('leek-fund.stocks') || []; - return this.service.getData(stockCodes, this.order).then(() => { - return this.getRootNodes(); - }); + return this.getRootNodes(globalState.stockGroups, globalState.stockLists); } else { - const resultPromise = Promise.resolve(this.service.stockList || []); - switch ( - element.id // First-level - ) { - case StockCategory.A: - return this.getAStockNodes(resultPromise); - case StockCategory.HK: - return this.getHkStockNodes(resultPromise); - case StockCategory.US: - return this.getUsStockNodes(resultPromise); - case StockCategory.Future: - return this.getFutureStockNodes(resultPromise); - case StockCategory.OverseaFuture: - return this.getOverseaFutureStockNodes(resultPromise); - case StockCategory.NODATA: - return this.getNoDataStockNodes(resultPromise); - default: - return []; - // return this.getChildrenNodesById(element.id); - } + return this.getChildrenNodes(element, globalState.stockLists); } } + getChildrenNodes(element: LeekTreeItem, stockLists: Array>): Promise> { + const groupId = element.id || ''; + const index: number = parseInt(groupId.replace('stockGroup_', '')); + const stockCodes: Array = stockLists[index]; + return this.service.getData(stockCodes, this.order, groupId); + } + getParent(): LeekTreeItem | undefined { return undefined; } @@ -90,7 +74,26 @@ export class StockProvider implements TreeDataProvider { } } - getRootNodes(): LeekTreeItem[] { + getRootNodes(stockGroups: Array, stockLists: Array>): Array { + let nodes: Array = []; + stockLists.forEach((value, index) => { + nodes.push( + new LeekTreeItem( + Object.assign({ contextValue: 'category' }, defaultFundInfo, { + id: `stockGroup_${index}`, + name: `${stockGroups[index]}${value.length > 0 ? `(${value.length})` : ''}`, + isStock: true, + }), + undefined, + true + ) + ); + }); + return nodes; + } + + // hide + getRootNodes1(): LeekTreeItem[] { const nodes = [ new LeekTreeItem( Object.assign({ contextValue: 'category' }, defaultFundInfo, { diff --git a/src/explorer/stockService.ts b/src/explorer/stockService.ts index 2df14e7b..7401f7d3 100644 --- a/src/explorer/stockService.ts +++ b/src/explorer/stockService.ts @@ -40,7 +40,11 @@ export default class StockService extends LeekService { return this.token; } - async getData(codes: Array, order: number): Promise> { + async getData( + codes: Array, + order: number, + groupId: string, + ): Promise> { // console.log('fetching stock data…'); if ((codes && codes.length === 0) || !codes) { return []; @@ -70,8 +74,8 @@ export default class StockService extends LeekService { let stockList: Array = []; const result = await Promise.allSettled([ - this.getStockData(stockCodes), - this.getHKStockData(hkCodes), + this.getStockData(stockCodes, groupId), + this.getHKStockData(hkCodes, groupId), ]); result.forEach((item) => { if (item.status === 'fulfilled') { @@ -88,7 +92,7 @@ export default class StockService extends LeekService { return res; } - async getStockData(codes: Array): Promise> { + async getStockData(codes: Array, groupId: string): Promise> { if ((codes && codes.length === 0) || !codes) { return []; } @@ -124,7 +128,7 @@ export default class StockService extends LeekService { return []; } for (const code of codes) { - stockList = stockList.concat(await this.getStockData(new Array(code))); + stockList = stockList.concat(await this.getStockData(new Array(code), groupId)); } } else { const splitData = resp.data.split(';\n'); @@ -349,6 +353,7 @@ export default class StockService extends LeekService { } catch (err) { console.error(err); } + stockItem.id = `${groupId}_${code}`; stockItem.showLabel = this.showLabel; stockItem.isStock = true; stockItem.type = type; @@ -365,7 +370,7 @@ export default class StockService extends LeekService { // 接口不支持的 noDataStockCount += 1; stockItem = { - id: code, + id: `${groupId}_${code}`, name: `接口不支持该股票 ${code}`, showLabel: this.showLabel, isStock: true, @@ -399,7 +404,7 @@ export default class StockService extends LeekService { return stockList; } - async getHKStockData(codes: Array): Promise> { + async getHKStockData(codes: Array, groupId: string): Promise> { if ((codes && codes.length === 0) || !codes) { return []; } @@ -463,6 +468,7 @@ export default class StockService extends LeekService { if (Number(open) <= 0) { price = yestclose; } + stockItem.id = `${groupId}_${stockItem.code}`; stockItem.showLabel = this.showLabel; stockItem.isStock = true; stockItem.type = 'hk'; diff --git a/src/extension.ts b/src/extension.ts index df6ee68b..fcb44d67 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -100,10 +100,13 @@ export function activate(context: ExtensionContext) { const manualRequest = () => { const fundLists = LeekFundConfig.getConfig('leek-fund.funds') || []; fundLists.forEach((value: Array, index: number) => { - fundService.getData(value, SortType.NORMAL, `fundGroup_${index}`); + Array.isArray(value) && fundService.getData(value, SortType.NORMAL, `fundGroup_${index}`); }); - stockService.getData(LeekFundConfig.getConfig('leek-fund.stocks'), SortType.NORMAL); + const stockLists = LeekFundConfig.getConfig('leek-fund.stocks') || []; + stockLists.forEach((value: Array, index: number) => { + Array.isArray(value) && stockService.getData(value, SortType.NORMAL, `stockGroup_${index}`); + }); }; manualRequest(); @@ -253,6 +256,19 @@ function setGlobalVariable() { } else { globalState.fundLists = fundLists; } + + globalState.stockGroups = LeekFundConfig.getConfig('leek-fund.stockGroups') || []; + + const stockLists = LeekFundConfig.getConfig('leek-fund.stocks') || []; + if (typeof stockLists[0] === 'string' || stockLists[0] instanceof String) { + // 迁移用户的股票代码到分组模式 + const newStockLists = [stockLists]; + globalState.stockLists = newStockLists; + LeekFundConfig.setConfig('leek-fund.stocks', newStockLists); + LeekFundConfig.setConfig('leek-fund.stockGroups', ['My Stocks']); + } else { + globalState.stockLists = stockLists; + } } // this method is called when your extension is deactivated diff --git a/src/globalState.ts b/src/globalState.ts index c48d15d7..6680e0da 100644 --- a/src/globalState.ts +++ b/src/globalState.ts @@ -38,6 +38,9 @@ let isDevelopment = false; // 是否开发环境 let fundGroups: Array = []; let fundLists: Array> = []; +let stockGroups: Array = []; +let stockLists: Array> = []; + let stockPrice = {}; // 缓存数据 let stockPriceCacheDate = '2020-10-30'; export default { @@ -69,6 +72,8 @@ export default { isDevelopment, fundGroups, fundLists, + stockGroups, + stockLists, stockPrice, stockPriceCacheDate, diff --git a/src/output/flash-news/impl/Jin10FlushService.ts b/src/output/flash-news/impl/Jin10FlushService.ts index 8993fc98..24bf9f37 100644 --- a/src/output/flash-news/impl/Jin10FlushService.ts +++ b/src/output/flash-news/impl/Jin10FlushService.ts @@ -44,7 +44,7 @@ export default class Jin10FlushService extends NewsFlushServiceAbstractClass { ws.addEventListener('error', (err: any) => { globalState.telemetry.sendEvent('error:jin10Service', { error: err }); - console.log("🚀 ~ 金十快讯 ws 错误:Jin10FlushService ~ ws.addEventListener ~ err:", err) + console.log("🚀 ~ 金十快讯 ws 错误:Jin10FlushService ~ ws.addEventListener ~ err:", err); }); ws.addEventListener('close', () => { diff --git a/src/registerCommand.ts b/src/registerCommand.ts index 0824c2b2..504aeaaf 100644 --- a/src/registerCommand.ts +++ b/src/registerCommand.ts @@ -131,6 +131,8 @@ export function registerViewEvent( // Stock operation commands.registerCommand('leek-fund.refreshStock', () => { + globalState.stockGroups = LeekFundConfig.getConfig('leek-fund.stockGroups', []); + globalState.stockLists = LeekFundConfig.getConfig('leek-fund.stocks', []); stockProvider.refresh(); const handler = window.setStatusBarMessage(`股票数据已刷新`); setTimeout(() => { @@ -154,7 +156,7 @@ export function registerViewEvent( } leekCenterView(stockService, fundService); }); - commands.registerCommand('leek-fund.addStock', () => { + commands.registerCommand('leek-fund.addStock', (target) => { // vscode QuickPick 不支持动态查询,只能用此方式解决 // https://github.com/microsoft/vscode/issues/23633 const qp = window.createQuickPick(); @@ -185,7 +187,7 @@ export function registerViewEvent( } // 存储到配置的时候是接口的参数格式,接口请求时不需要再转换 const newCode = code.replace('gb', 'gb_').replace('us', 'usr_'); - LeekFundConfig.updateStockCfg(newCode, () => { + LeekFundConfig.addStockCfg(target.id, newCode, () => { stockProvider.refresh(); }); qp.hide(); @@ -196,6 +198,32 @@ export function registerViewEvent( stockProvider.changeOrder(); stockProvider.refresh(); }); + commands.registerCommand('leek-fund.addStockGroup', () => { + window.showInputBox({ placeHolder: '请输入股票分组名称' }).then((name) => { + if (!name) { + return; + } + LeekFundConfig.addStockGroupCfg(name, () => { + stockProvider.refresh(); + }); + }); + }); + commands.registerCommand('leek-fund.removeStockGroup', (target) => { + LeekFundConfig.removeStockGroupCfg(target.id, () => { + stockService.stockList = []; + stockProvider.refresh(); + }); + }); + commands.registerCommand('leek-fund.renameStockGroup', (target) => { + window.showInputBox({ placeHolder: '请输入股票分组名称' }).then((name) => { + if (!name) { + return; + } + LeekFundConfig.renameStockGroupCfg(target.id, name, () => { + stockProvider.refresh(); + }); + }); + }); /** * WebView diff --git a/src/shared/leekConfig.ts b/src/shared/leekConfig.ts index 2d2b104b..7d9a6ef9 100644 --- a/src/shared/leekConfig.ts +++ b/src/shared/leekConfig.ts @@ -146,13 +146,64 @@ export class LeekFundConfig extends BaseConfig { // Fund End // Stock Begin - static updateStockCfg(codes: string, cb?: Function) { - this.updateConfig('leek-fund.stocks', codes.split(',')).then(() => { - window.showInformationMessage(`Stock Successfully add.`); + static addStockGroupCfg(name: string, cb?: Function) { + globalState.stockGroups.push(name); + globalState.stockLists.push([]); + this.setConfig('leek-fund.stockGroups', globalState.stockGroups); + this.setConfig('leek-fund.stocks', globalState.stockLists); + window.showInformationMessage(`Stock Group Successfully add.`); + if (cb && typeof cb === 'function') { + cb(name); + } + } + + static renameStockGroupCfg(groupId: string, name: string, cb?: Function) { + const index: number = parseInt(groupId.replace('stockGroup_', '')); + globalState.stockGroups[index] = name; + this.setConfig('leek-fund.stockGroups', globalState.stockGroups); + window.showInformationMessage(`Stock Group Successfully rename.`); + if (cb && typeof cb === 'function') { + cb(groupId); + } + } + + static removeStockGroupCfg(groupId: string, cb?: Function) { + const index: number = parseInt(groupId.replace('stockGroup_', '')); + const removedStockList: Array = globalState.stockLists[index]; + const removeStockGroup = () => { + globalState.stockGroups.splice(index, 1); + globalState.stockLists.splice(index, 1); + this.setConfig('leek-fund.stockGroups', globalState.stockGroups); + this.setConfig('leek-fund.stocks', globalState.stockLists); + window.showInformationMessage(`Stock Group Successfully delete.`); if (cb && typeof cb === 'function') { - cb(codes); + cb(groupId); } - }); + }; + + if (removedStockList.length) { + window.showInformationMessage('删除分组会清空股票数据无法恢复,请确认!!', '好的', '取消').then((res) => { + if (res === '好的') { + removeStockGroup(); + } + }); + } else { + removeStockGroup(); + } + } + + static addStockCfg(groupId: string, codes: string, cb?: Function) { + const index: number = parseInt(groupId.replace('stockGroup_', '')); + const stocks = globalState.stockLists[index] as Array; + let updatedStocks = [...stocks, ...codes.split(',')]; + updatedStocks = clean(updatedStocks); + updatedStocks = uniq(updatedStocks); + globalState.stockLists[index] = updatedStocks as never; + this.setConfig('leek-fund.stocks', globalState.stockLists); + window.showInformationMessage(`Fund Successfully add.`); + if (cb && typeof cb === 'function') { + cb(codes); + } } static removeStockCfg(code: string, cb?: Function) { @@ -198,7 +249,7 @@ export class LeekFundConfig extends BaseConfig { } static setStockTopCfg(code: string, cb?: Function) { - let configArr: string[] = this.getConfig('leek-fund.stocks'); + let configArr: string[] = this.getConfig('leek-fund.stocks').flat(); configArr = [code, ...configArr.filter((item) => item !== code)]; @@ -218,7 +269,7 @@ export class LeekFundConfig extends BaseConfig { } }; - let configArr: string[] = this.getConfig('leek-fund.stocks'); + let configArr: string[] = this.getConfig('leek-fund.stocks').flat(); const currentIndex = configArr.indexOf(code); let previousIndex = currentIndex - 1; // 找到前一个同市场的股票 @@ -264,7 +315,7 @@ export class LeekFundConfig extends BaseConfig { } }; - let configArr: string[] = this.getConfig('leek-fund.stocks'); + let configArr: string[] = this.getConfig('leek-fund.stocks').flat(); const currentIndex = configArr.indexOf(code); let nextIndex = currentIndex + 1; //找到后一个同市场的股票 diff --git a/src/shared/utils.ts b/src/shared/utils.ts index b590e3bd..b7c33ca9 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -338,7 +338,7 @@ export function allMarkets(): Array { result.push(StockCategory.A); } - const stocks: Array = LeekFundConfig.getConfig('leek-fund.stocks'); + const stocks: Array = LeekFundConfig.getConfig('leek-fund.stocks').flat(); stocks.forEach((item: string) => { let market = StockCategory.NODATA; if (/^(sh|sz|bj)/.test(item)) {