Compare commits

..

No commits in common. "master" and "v2025.4.25.3-alpha" have entirely different histories.

52 changed files with 28437 additions and 207777 deletions

View File

@ -4,7 +4,7 @@ on:
push: push:
tags: tags:
# Match any new tag # Match any new tag
- '*-release' - '*'
env: env:
# Necessary for most environments as build failure can occur due to OOM issues # Necessary for most environments as build failure can occur due to OOM issues
@ -47,4 +47,4 @@ jobs:
go-version: '1.24' go-version: '1.24'
build-tags: ${{ github.ref_name }} build-tags: ${{ github.ref_name }}
build-commit-message: ${{ steps.get_commit_message.outputs.commit_message }} build-commit-message: ${{ steps.get_commit_message.outputs.commit_message }}
node-version: '20.x' node-version: '18.x'

View File

@ -1,17 +1,16 @@
# go-stock : 基于大语言模型的AI赋能股票分析工具 # go-stock : 基于Wails和NaiveUI构建的AI赋能股票分析工具
## ![go-stock](./build/appicon.png) ## ![go-stock](./build/appicon.png)
![GitHub Release](https://img.shields.io/github/v/release/ArvinLovegood/go-stock?link=https%3A%2F%2Fgithub.com%2FArvinLovegood%2Fgo-stock%2Freleases&link=https%3A%2F%2Fgithub.com%2FArvinLovegood%2Fgo-stock%2Freleases) ![GitHub Release](https://img.shields.io/github/v/release/ArvinLovegood/go-stock?link=https%3A%2F%2Fgithub.com%2FArvinLovegood%2Fgo-stock%2Freleases&link=https%3A%2F%2Fgithub.com%2FArvinLovegood%2Fgo-stock%2Freleases)
[![GitHub Repo stars](https://img.shields.io/github/stars/ArvinLovegood/go-stock?link=https%3A%2F%2Fgithub.com%2FArvinLovegood%2Fgo-stock)](https://github.com/ArvinLovegood/go-stock) [![GitHub Repo stars](https://img.shields.io/github/stars/ArvinLovegood/go-stock?link=https%3A%2F%2Fgithub.com%2FArvinLovegood%2Fgo-stock)](https://github.com/ArvinLovegood/go-stock)
[![star](https://gitee.com/arvinlovegood_admin/go-stock/badge/star.svg?theme=dark)](https://gitee.com/arvinlovegood_admin/go-stock) [![star](https://gitee.com/arvinlovegood_admin/go-stock/badge/star.svg?theme=dark)](https://gitee.com/arvinlovegood_admin/go-stock)
[![star](https://gitcode.com/ArvinLovegood/go-stock/star/badge.svg)](https://gitcode.com/ArvinLovegood/go-stock)
[//]: # ([![star](https://gitcode.com/ArvinLovegood/go-stock/star/badge.svg)](https://gitcode.com/ArvinLovegood/go-stock))
### 🌟公众号 ### 🌟公众号
![扫码_搜索联合传播样式-白色版.png](build/screenshot/%E6%89%AB%E7%A0%81_%E6%90%9C%E7%B4%A2%E8%81%94%E5%90%88%E4%BC%A0%E6%92%AD%E6%A0%B7%E5%BC%8F-%E7%99%BD%E8%89%B2%E7%89%88.png) ![扫码_搜索联合传播样式-白色版.png](build/screenshot/%E6%89%AB%E7%A0%81_%E6%90%9C%E7%B4%A2%E8%81%94%E5%90%88%E4%BC%A0%E6%92%AD%E6%A0%B7%E5%BC%8F-%E7%99%BD%E8%89%B2%E7%89%88.png)
### 📈 交流群 ### 📈 交流群
- QQ交流群2[点击链接加入群聊【go-stock交流群2】892666282](https://qm.qq.com/q/5mYiy6Yxh0) QQ交流群[点击链接加入群聊【go-stock交流群491605333](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0YQ8qD3exahsD4YLNhzQTWe5ssstWC89&authKey=usOMMRFtIQDC%2FYcatHYapcxQbJ7PwXPHK9OypTXWzNjAq%2FRVvQu9bj2lRgb%2BSZ3p&noverify=0&group_code=491605333)
- QQ交流群[点击链接加入群聊【go-stock交流群】491605333(已满会定期清理,随缘入群)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0YQ8qD3exahsD4YLNhzQTWe5ssstWC89&authKey=usOMMRFtIQDC%2FYcatHYapcxQbJ7PwXPHK9OypTXWzNjAq%2FRVvQu9bj2lRgb%2BSZ3p&noverify=0&group_code=491605333)
### ✨ 简介 ### ✨ 简介
- 本项目基于Wails和NaiveUI开发结合AI大模型构建的股票分析工具。 - 本项目基于Wails和NaiveUI开发结合AI大模型构建的股票分析工具。
@ -27,16 +26,15 @@
### 💬 支持大模型/平台 ### 💬 支持大模型/平台
| 模型 | 状态 | 备注 | | 模型 | 状态 | 备注 |
| --- | --- |---------------------------------------------------------------------------------------------------------------------------------------------------------------| | --- | --- |-----------------------------------------------------------------------------------------------------------------------------------------------------|
| [OpenAI](https://platform.openai.com/) | ✅ | 可接入任何 OpenAI 接口格式模型 | | [OpenAI](https://platform.openai.com/) | ✅ | 可接入任何 OpenAI 接口格式模型 |
| [Ollama](https://ollama.com/) | ✅ | 本地大模型运行平台 | | [Ollama](https://ollama.com/) | ✅ | 本地大模型运行平台 |
| [LMStudio](https://lmstudio.ai/) | ✅ | 本地大模型运行平台 | | [LMStudio](https://lmstudio.ai/) | ✅ | 本地大模型运行平台 |
| [AnythingLLM](https://anythingllm.com/) | ✅ | 本地知识库 | | [AnythingLLM](https://anythingllm.com/) | ✅ | 本地知识库 |
| [DeepSeek](https://www.deepseek.com/) | ✅ | deepseek-reasoner,deepseek-chat | | [DeepSeek](https://www.deepseek.com/) | ✅ | deepseek-reasoner,deepseek-chat |
| [大模型聚合平台](https://cloud.siliconflow.cn/i/foufCerk) | ✅ | 如:[硅基流动](https://cloud.siliconflow.cn/i/foufCerk)[火山方舟](https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=IJSE43PZ) ,[优云智算](https://www.compshare.cn/image-community?ytag=GPU_YY-gh_gostock) | | [大模型聚合平台](https://cloud.siliconflow.cn/i/foufCerk) | ✅ | 如:[硅基流动](https://cloud.siliconflow.cn/i/foufCerk)[火山方舟](https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=IJSE43PZ) |
### <span style="color: #568DF4;">各位亲爱的朋友们,如果您对这个项目感兴趣,请先给我一个<i style="color: #EA2626;">star</i>吧,谢谢!</span>💕 ### <span style="color: #568DF4;">各位亲爱的朋友们,如果您对这个项目感兴趣,请先给我一个<i style="color: #EA2626;">star</i>吧,谢谢!</span>💕
- 优云智算by UCloud万卡规模4090免费用10小时新人注册另增50万tokens海量热门源项目镜像一键部署[注册链接](https://www.compshare.cn/image-community?ytag=GPU_YY-gh_gostock)
- 经测试目前硅基流动(siliconflow)提供的deepSeek api 服务比较稳定注册即送2000万Tokens[注册链接](https://cloud.siliconflow.cn/i/foufCerk) - 经测试目前硅基流动(siliconflow)提供的deepSeek api 服务比较稳定注册即送2000万Tokens[注册链接](https://cloud.siliconflow.cn/i/foufCerk)
- 火山方舟每个模型注册即送50万tokens[注册链接](https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=IJSE43PZ) - 火山方舟每个模型注册即送50万tokens[注册链接](https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=IJSE43PZ)
- Tushare大数据开放社区,免费提供各类金融数据,助力行业和量化研究(注意Tushare只需要120积分即可注册完成个人资料补充即可得120积分)[注册链接](https://tushare.pro/register?reg=701944) - Tushare大数据开放社区,免费提供各类金融数据,助力行业和量化研究(注意Tushare只需要120积分即可注册完成个人资料补充即可得120积分)[注册链接](https://tushare.pro/register?reg=701944)
@ -47,34 +45,14 @@
## 🧩 重大功能开发计划 ## 🧩 重大功能开发计划
| 功能说明 | 状态 | 备注 | | 功能说明 | 状态 | 备注 |
|-----------------|----|----------------------------------------------------------------------------------------------------------| |-----------------|----|----------------------------------------------------------------------------------------------------------|
| 股票分析知识库 | 🚧 | 未来计划 |
| Ai智能选股 | 🚧 | Ai智能选股功能开发中(下半年重点开发计划) |
| ETF支持 | 🚧 | ETF数据支持 (目前可以查看净值和估值) | | ETF支持 | 🚧 | ETF数据支持 (目前可以查看净值和估值) |
| 美股支持 | ✅ | 美股数据支持 | | 美股支持 | ✅ | 美股数据支持 |
| 港股支持 | ✅ | 港股数据支持 | | 港股支持 | ✅ | 港股数据支持 (目前有延迟) |
| 多轮对话 | ✅ | AI分析后可继续对话提问 | | 多轮对话 | ✅ | AI分析后可继续对话提问 |
| 自定义AI分析提问模板 | ✅ | 可配置的提问模板 [v2025.2.12.7-alpha](https://github.com/ArvinLovegood/go-stock/releases/tag/v2025.2.12.7-alpha) | | 自定义AI分析提问模板 | ✅ | 可配置的提问模板 [v2025.2.12.7-alpha](https://github.com/ArvinLovegood/go-stock/releases/tag/v2025.2.12.7-alpha) |
| 不再强制依赖Chrome浏览器 | ✅ | 默认使用edge浏览器抓取新闻资讯 | | 不再强制依赖Chrome浏览器 | ✅ | 默认使用edge浏览器抓取新闻资讯 |
## 👀 更新日志 ## 👀 更新日志
### 2025.06.30 添加指标选股功能
### 2025.06.27 添加财经日历和重大事件时间轴功能
### 2025.06.25 添加热门股票、事件和话题功能
### 2025.06.18 更新内置股票基础数据,软件内实时市场资讯信息提醒,添加行业研究功能
### 2025.06.15 添加公司公告信息搜索/查看功能
### 2025.06.15 添加个股研报到弹出菜单
### 2025.06.13 添加个股研报功能
### 2025.06.12 添加龙虎榜功能,新增行业排名分类
### 2025.05.30 优化股票分时图显示
### 2025.05.20 修复财联社电报获取问题
### 2025.05.16 优化资金趋势图表组件
### 2025.05.15 重构应用加载和数据初始化逻辑,添加股票资金趋势功能,资金趋势图表增加主力当日净流入数据并优化展示效果
### 2025.05.14 添加个股资金流向功能排行榜增加股票行情K线图弹窗
### 2025.05.13 添加行业排名功能
### 2025.05.09 添加A股盘口数据解析和展示功能
### 2025.05.07 优化分时图的展示
### 2025.04.29 补全港股/美股基础数据,优化港股股价延迟问题,优化初始化逻辑
### 2025.04.25 市场资讯支持AI分析和总结让AI帮你读市场
### 2025.04.24 新增市场行情模块:即时掌握全球市场行情资讯/动态从此再也不用偷摸去各大财经网站啦。go-stock一键帮你搞定 ### 2025.04.24 新增市场行情模块:即时掌握全球市场行情资讯/动态从此再也不用偷摸去各大财经网站啦。go-stock一键帮你搞定
### 2025.04.22 优化K线图展示支持拉伸放大看得更舒服啦 ### 2025.04.22 优化K线图展示支持拉伸放大看得更舒服啦
### 2025.04.21 港股美股K线数据获取优化 ### 2025.04.21 港股美股K线数据获取优化
@ -100,8 +78,6 @@
## 🦄 重大更新 ## 🦄 重大更新
### BIG NEWS !!! 重大更新!!! ### BIG NEWS !!! 重大更新!!!
- 2025.04.25 市场资讯支持AI分析和总结让AI帮你读市场
![img.png](img.png)
- 2025.04.24 新增市场行情模块:即时掌握全球市场行情资讯/动态从此再也不用偷摸去各大财经网站啦。go-stock一键帮你搞定 - 2025.04.24 新增市场行情模块:即时掌握全球市场行情资讯/动态从此再也不用偷摸去各大财经网站啦。go-stock一键帮你搞定
![img.png](build/screenshot/img13.png) ![img.png](build/screenshot/img13.png)
![img_13.png](build/screenshot/img_13.png) ![img_13.png](build/screenshot/img_13.png)

67
app.go
View File

@ -139,22 +139,6 @@ func (a *App) CheckUpdate() {
func (a *App) domReady(ctx context.Context) { func (a *App) domReady(ctx context.Context) {
defer PanicHandler() defer PanicHandler()
if stocksBin != nil && len(stocksBin) > 0 {
go runtime.EventsEmit(a.ctx, "loadingMsg", "检查A股基础信息...")
go initStockData(a.ctx)
}
if stocksBinHK != nil && len(stocksBinHK) > 0 {
go runtime.EventsEmit(a.ctx, "loadingMsg", "检查港股基础信息...")
go initStockDataHK(a.ctx)
}
if stocksBinUS != nil && len(stocksBinUS) > 0 {
go runtime.EventsEmit(a.ctx, "loadingMsg", "检查美股基础信息...")
go initStockDataUS(a.ctx)
}
updateBasicInfo()
// Add your action here // Add your action here
//定时更新数据 //定时更新数据
config := data.NewSettingsApi(&data.Settings{}).GetConfig() config := data.NewSettingsApi(&data.Settings{}).GetConfig()
@ -178,9 +162,6 @@ func (a *App) domReady(ctx context.Context) {
} }
entryID, err := a.cron.AddFunc(fmt.Sprintf("@every %ds", interval+10), func() { entryID, err := a.cron.AddFunc(fmt.Sprintf("@every %ds", interval+10), func() {
news := data.NewMarketNewsApi().GetNewTelegraph(30) news := data.NewMarketNewsApi().GetNewTelegraph(30)
if config.EnablePushNews {
go a.NewsPush(news)
}
go runtime.EventsEmit(a.ctx, "newTelegraph", news) go runtime.EventsEmit(a.ctx, "newTelegraph", news)
}) })
if err != nil { if err != nil {
@ -191,9 +172,6 @@ func (a *App) domReady(ctx context.Context) {
entryIDSina, err := a.cron.AddFunc(fmt.Sprintf("@every %ds", interval+10), func() { entryIDSina, err := a.cron.AddFunc(fmt.Sprintf("@every %ds", interval+10), func() {
news := data.NewMarketNewsApi().GetSinaNews(30) news := data.NewMarketNewsApi().GetSinaNews(30)
if config.EnablePushNews {
go a.NewsPush(news)
}
go runtime.EventsEmit(a.ctx, "newSinaNews", news) go runtime.EventsEmit(a.ctx, "newSinaNews", news)
}) })
if err != nil { if err != nil {
@ -298,15 +276,6 @@ func (a *App) domReady(ctx context.Context) {
} }
func (a *App) NewsPush(news *[]models.Telegraph) {
for _, telegraph := range *news {
//if telegraph.IsRed {
go runtime.EventsEmit(a.ctx, "newsPush", telegraph)
go data.NewAlertWindowsApi("go-stock", telegraph.Source+" "+telegraph.Time, telegraph.Content, string(icon)).SendNotification()
//}
}
}
func (a *App) AddCronTask(follow data.FollowedStock) func() { func (a *App) AddCronTask(follow data.FollowedStock) func() {
return func() { return func() {
go runtime.EventsEmit(a.ctx, "warnMsg", "开始自动分析"+follow.Name+"_"+follow.StockCode) go runtime.EventsEmit(a.ctx, "warnMsg", "开始自动分析"+follow.Name+"_"+follow.StockCode)
@ -1096,17 +1065,6 @@ func (a *App) RemoveGroup(groupId int) string {
func (a *App) GetStockKLine(stockCode, stockName string, days int64) *[]data.KLineData { func (a *App) GetStockKLine(stockCode, stockName string, days int64) *[]data.KLineData {
return data.NewStockDataApi().GetHK_KLineData(stockCode, "day", days) return data.NewStockDataApi().GetHK_KLineData(stockCode, "day", days)
} }
func (a *App) GetStockMinutePriceLineData(stockCode, stockName string) map[string]any {
res := make(map[string]any, 4)
priceData, date := data.NewStockDataApi().GetStockMinutePriceData(stockCode)
res["priceData"] = priceData
res["date"] = date
res["stockName"] = stockName
res["stockCode"] = stockCode
return res
}
func (a *App) GetStockCommonKLine(stockCode, stockName string, days int64) *[]data.KLineData { func (a *App) GetStockCommonKLine(stockCode, stockName string, days int64) *[]data.KLineData {
return data.NewStockDataApi().GetCommonKLineData(stockCode, "day", days) return data.NewStockDataApi().GetCommonKLineData(stockCode, "day", days)
} }
@ -1116,13 +1074,6 @@ func (a *App) GetTelegraphList(source string) *[]*models.Telegraph {
return telegraphs return telegraphs
} }
func (a *App) ReFleshTelegraphList(source string) *[]*models.Telegraph {
data.NewMarketNewsApi().GetNewTelegraph(30)
data.NewMarketNewsApi().GetSinaNews(30)
telegraphs := data.NewMarketNewsApi().GetTelegraphList(source)
return telegraphs
}
func (a *App) GlobalStockIndexes() map[string]any { func (a *App) GlobalStockIndexes() map[string]any {
return data.NewMarketNewsApi().GlobalStockIndexes(30) return data.NewMarketNewsApi().GlobalStockIndexes(30)
} }
@ -1134,21 +1085,3 @@ func (a *App) SummaryStockNews(question string, sysPromptId *int) {
} }
runtime.EventsEmit(a.ctx, "summaryStockNews", "DONE") runtime.EventsEmit(a.ctx, "summaryStockNews", "DONE")
} }
func (a *App) GetIndustryRank(sort string, cnt int) []any {
res := data.NewMarketNewsApi().GetIndustryRank(sort, cnt)
return res["data"].([]any)
}
func (a *App) GetIndustryMoneyRankSina(fenlei, sort string) []map[string]any {
res := data.NewMarketNewsApi().GetIndustryMoneyRankSina(fenlei, sort)
return res
}
func (a *App) GetMoneyRankSina(sort string) []map[string]any {
res := data.NewMarketNewsApi().GetMoneyRankSina(sort)
return res
}
func (a *App) GetStockMoneyTrendByDay(stockCode string, days int) []map[string]any {
res := data.NewMarketNewsApi().GetStockMoneyTrendByDay(stockCode, days)
slice.Reverse(res)
return res
}

View File

@ -1,61 +0,0 @@
package main
import (
"go-stock/backend/data"
"go-stock/backend/models"
)
// @Author spark
// @Date 2025/6/8 20:45
// @Desc
//-----------------------------------------------------------------------------------
func (a *App) LongTigerRank(date string) *[]models.LongTigerRankData {
return data.NewMarketNewsApi().LongTiger(date)
}
func (a *App) StockResearchReport(stockCode string) []any {
return data.NewMarketNewsApi().StockResearchReport(stockCode, 7)
}
func (a *App) StockNotice(stockCode string) []any {
return data.NewMarketNewsApi().StockNotice(stockCode)
}
func (a *App) IndustryResearchReport(industryCode string) []any {
return data.NewMarketNewsApi().IndustryResearchReport(industryCode, 7)
}
func (a App) EMDictCode(code string) []any {
return data.NewMarketNewsApi().EMDictCode(code, a.cache)
}
func (a App) AnalyzeSentiment(text string) data.SentimentResult {
return data.AnalyzeSentiment(text)
}
func (a App) HotStock(marketType string) *[]models.HotItem {
return data.NewMarketNewsApi().XUEQIUHotStock(100, marketType)
}
func (a App) HotEvent(size int) *[]models.HotEvent {
if size <= 0 {
size = 10
}
return data.NewMarketNewsApi().HotEvent(size)
}
func (a App) HotTopic(size int) []any {
if size <= 0 {
size = 10
}
return data.NewMarketNewsApi().HotTopic(size)
}
func (a App) InvestCalendarTimeLine(yearMonth string) []any {
return data.NewMarketNewsApi().InvestCalendar(yearMonth)
}
func (a App) ClsCalendar() []any {
return data.NewMarketNewsApi().ClsCalendar()
}
func (a App) SearchStock(words string) map[string]any {
return data.NewSearchStockApi(words).SearchStock()
}

View File

@ -1,17 +1,13 @@
package data package data
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/coocood/freecache"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/strutil" "github.com/duke-git/lancet/v2/strutil"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/tidwall/gjson"
"go-stock/backend/db" "go-stock/backend/db"
"go-stock/backend/logger" "go-stock/backend/logger"
"go-stock/backend/models" "go-stock/backend/models"
@ -41,7 +37,7 @@ func (m MarketNewsApi) GetNewTelegraph(crawlTimeOut int64) *[]models.Telegraph {
//logger.SugaredLogger.Info(string(response.Body())) //logger.SugaredLogger.Info(string(response.Body()))
document, _ := goquery.NewDocumentFromReader(strings.NewReader(string(response.Body()))) document, _ := goquery.NewDocumentFromReader(strings.NewReader(string(response.Body())))
document.Find(".telegraph-content-box").Each(func(i int, selection *goquery.Selection) { document.Find(".telegraph-list").Each(func(i int, selection *goquery.Selection) {
//logger.SugaredLogger.Info(selection.Text()) //logger.SugaredLogger.Info(selection.Text())
telegraph := models.Telegraph{Source: "财联社电报"} telegraph := models.Telegraph{Source: "财联社电报"}
spans := selection.Find("div.telegraph-content-box span") spans := selection.Find("div.telegraph-content-box span")
@ -73,7 +69,6 @@ func (m MarketNewsApi) GetNewTelegraph(crawlTimeOut int64) *[]models.Telegraph {
//telegraph = append(telegraph, ReplaceSensitiveWords(selection.Text())) //telegraph = append(telegraph, ReplaceSensitiveWords(selection.Text()))
if telegraph.Content != "" { if telegraph.Content != "" {
telegraph.SentimentResult = AnalyzeSentiment(telegraph.Content).Description
cnt := int64(0) cnt := int64(0)
db.Dao.Model(telegraph).Where("time=? and source=?", telegraph.Time, telegraph.Source).Count(&cnt) db.Dao.Model(telegraph).Where("time=? and source=?", telegraph.Time, telegraph.Source).Count(&cnt)
if cnt == 0 { if cnt == 0 {
@ -143,7 +138,8 @@ func (m MarketNewsApi) GetSinaNews(crawlTimeOut uint) *[]models.Telegraph {
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60"). SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
Get("https://zhibo.sina.com.cn/api/zhibo/feed?callback=callback&page=1&page_size=20&zhibo_id=152&tag_id=0&dire=f&dpc=1&pagesize=20&id=4161089&type=0&_=" + strconv.FormatInt(time.Now().Unix(), 10)) Get("https://zhibo.sina.com.cn/api/zhibo/feed?callback=callback&page=1&page_size=20&zhibo_id=152&tag_id=0&dire=f&dpc=1&pagesize=20&id=4161089&type=0&_=" + strconv.FormatInt(time.Now().Unix(), 10))
js := string(response.Body()) js := string(response.Body())
js = strutil.ReplaceWithMap(js, map[string]string{ js = strutil.ReplaceWithMap(js,
map[string]string{
"try{callback(": "var data=", "try{callback(": "var data=",
");}catch(e){};": ";", ");}catch(e){};": ";",
}) })
@ -193,7 +189,6 @@ func (m MarketNewsApi) GetSinaNews(crawlTimeOut uint) *[]models.Telegraph {
logger.SugaredLogger.Infof("telegraph.SubjectTags:%v %s", telegraph.SubjectTags, telegraph.Content) logger.SugaredLogger.Infof("telegraph.SubjectTags:%v %s", telegraph.SubjectTags, telegraph.Content)
if telegraph.Content != "" { if telegraph.Content != "" {
telegraph.SentimentResult = AnalyzeSentiment(telegraph.Content).Description
cnt := int64(0) cnt := int64(0)
db.Dao.Model(telegraph).Where("time=? and source=?", telegraph.Time, telegraph.Source).Count(&cnt) db.Dao.Model(telegraph).Where("time=? and source=?", telegraph.Time, telegraph.Source).Count(&cnt)
if cnt == 0 { if cnt == 0 {
@ -229,475 +224,3 @@ func (m MarketNewsApi) GlobalStockIndexes(crawlTimeOut uint) map[string]any {
json.Unmarshal([]byte(js), &res) json.Unmarshal([]byte(js), &res)
return res["data"].(map[string]any) return res["data"].(map[string]any)
} }
func (m MarketNewsApi) GetIndustryRank(sort string, cnt int) map[string]any {
url := fmt.Sprintf("https://proxy.finance.qq.com/ifzqgtimg/appstock/app/mktHs/rank?l=%d&p=1&t=01/averatio&ordertype=&o=%s", cnt, sort)
response, _ := resty.New().SetTimeout(time.Duration(5)*time.Second).R().
SetHeader("Referer", "https://stockapp.finance.qq.com/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
Get(url)
js := string(response.Body())
res := make(map[string]any)
json.Unmarshal([]byte(js), &res)
return res
}
func (m MarketNewsApi) GetIndustryMoneyRankSina(fenlei, sort string) []map[string]any {
url := fmt.Sprintf("https://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/MoneyFlow.ssl_bkzj_bk?page=1&num=20&sort=%s&asc=0&fenlei=%s", sort, fenlei)
response, _ := resty.New().SetTimeout(time.Duration(5)*time.Second).R().
SetHeader("Host", "vip.stock.finance.sina.com.cn").
SetHeader("Referer", "https://finance.sina.com.cn").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
Get(url)
js := string(response.Body())
res := &[]map[string]any{}
err := json.Unmarshal([]byte(js), &res)
if err != nil {
logger.SugaredLogger.Error(err)
return *res
}
return *res
}
func (m MarketNewsApi) GetMoneyRankSina(sort string) []map[string]any {
if sort == "" {
sort = "netamount"
}
url := fmt.Sprintf("https://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/MoneyFlow.ssl_bkzj_ssggzj?page=1&num=20&sort=%s&asc=0&bankuai=&shichang=", sort)
response, _ := resty.New().SetTimeout(time.Duration(5)*time.Second).R().
SetHeader("Host", "vip.stock.finance.sina.com.cn").
SetHeader("Referer", "https://finance.sina.com.cn").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
Get(url)
js := string(response.Body())
res := &[]map[string]any{}
err := json.Unmarshal([]byte(js), &res)
if err != nil {
logger.SugaredLogger.Error(err)
return *res
}
return *res
}
func (m MarketNewsApi) GetStockMoneyTrendByDay(stockCode string, days int) []map[string]any {
url := fmt.Sprintf("http://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/MoneyFlow.ssl_qsfx_zjlrqs?page=1&num=%d&sort=opendate&asc=0&daima=%s", days, stockCode)
response, _ := resty.New().SetTimeout(time.Duration(5)*time.Second).R().
SetHeader("Host", "vip.stock.finance.sina.com.cn").
SetHeader("Referer", "https://finance.sina.com.cn").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").Get(url)
js := string(response.Body())
res := &[]map[string]any{}
err := json.Unmarshal([]byte(js), &res)
if err != nil {
logger.SugaredLogger.Error(err)
return *res
}
return *res
}
func (m MarketNewsApi) TopStocksRankingList(date string) {
url := fmt.Sprintf("http://vip.stock.finance.sina.com.cn/q/go.php/vInvestConsult/kind/lhb/index.phtml?tradedate=%s", date)
response, _ := resty.New().SetTimeout(time.Duration(5)*time.Second).R().
SetHeader("Host", "vip.stock.finance.sina.com.cn").
SetHeader("Referer", "https://finance.sina.com.cn").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").Get(url)
html, _ := convertor.GbkToUtf8(response.Body())
//logger.SugaredLogger.Infof("html:%s", html)
document, err := goquery.NewDocumentFromReader(bytes.NewReader(html))
if err != nil {
return
}
document.Find("table.list_table").Each(func(i int, s *goquery.Selection) {
title := strutil.Trim(s.Find("tr:first-child").First().Text())
logger.SugaredLogger.Infof("title:%s", title)
s.Find("tr:not(:first-child)").Each(func(i int, s *goquery.Selection) {
logger.SugaredLogger.Infof("s:%s", strutil.RemoveNonPrintable(s.Text()))
})
})
}
func (m MarketNewsApi) LongTiger(date string) *[]models.LongTigerRankData {
ranks := &[]models.LongTigerRankData{}
url := "https://datacenter-web.eastmoney.com/api/data/v1/get"
logger.SugaredLogger.Infof("url:%s", url)
params := make(map[string]string)
params["callback"] = "callback"
params["sortColumns"] = "TURNOVERRATE,TRADE_DATE,SECURITY_CODE"
params["sortTypes"] = "-1,-1,1"
params["pageSize"] = "500"
params["pageNumber"] = "1"
params["reportName"] = "RPT_DAILYBILLBOARD_DETAILSNEW"
params["columns"] = "SECURITY_CODE,SECUCODE,SECURITY_NAME_ABBR,TRADE_DATE,EXPLAIN,CLOSE_PRICE,CHANGE_RATE,BILLBOARD_NET_AMT,BILLBOARD_BUY_AMT,BILLBOARD_SELL_AMT,BILLBOARD_DEAL_AMT,ACCUM_AMOUNT,DEAL_NET_RATIO,DEAL_AMOUNT_RATIO,TURNOVERRATE,FREE_MARKET_CAP,EXPLANATION,D1_CLOSE_ADJCHRATE,D2_CLOSE_ADJCHRATE,D5_CLOSE_ADJCHRATE,D10_CLOSE_ADJCHRATE,SECURITY_TYPE_CODE"
params["source"] = "WEB"
params["client"] = "WEB"
params["filter"] = fmt.Sprintf("(TRADE_DATE<='%s')(TRADE_DATE>='%s')", date, date)
resp, err := resty.New().SetTimeout(time.Duration(15)*time.Second).R().
SetHeader("Host", "datacenter-web.eastmoney.com").
SetHeader("Referer", "https://data.eastmoney.com/stock/tradedetail.html").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
SetQueryParams(params).
Get(url)
if err != nil {
return ranks
}
js := string(resp.Body())
logger.SugaredLogger.Infof("resp:%s", js)
js = strutil.ReplaceWithMap(js, map[string]string{
"callback(": "var data=",
");": ";",
})
//logger.SugaredLogger.Info(js)
vm := otto.New()
_, err = vm.Run(js)
_, err = vm.Run("var data = JSON.stringify(data);")
value, err := vm.Get("data")
logger.SugaredLogger.Infof("resp-json:%s", value.String())
data := gjson.Get(value.String(), "result.data")
logger.SugaredLogger.Infof("resp:%v", data)
err = json.Unmarshal([]byte(data.String()), ranks)
if err != nil {
logger.SugaredLogger.Error(err)
return ranks
}
for _, rankData := range *ranks {
temp := &models.LongTigerRankData{}
db.Dao.Model(temp).Where(&models.LongTigerRankData{
TRADEDATE: rankData.TRADEDATE,
SECUCODE: rankData.SECUCODE,
}).First(temp)
if temp.SECURITYTYPECODE == "" {
db.Dao.Model(temp).Create(&rankData)
}
}
return ranks
}
func (m MarketNewsApi) IndustryResearchReport(industryCode string, days int) []any {
beginDate := time.Now().Add(-time.Duration(days) * 24 * time.Hour).Format("2006-01-02")
endDate := time.Now().Format("2006-01-02")
if strutil.Trim(industryCode) != "" {
beginDate = time.Now().Add(-time.Duration(days) * 365 * time.Hour).Format("2006-01-02")
}
logger.SugaredLogger.Infof("IndustryResearchReport-name:%s", industryCode)
params := map[string]string{
"industry": "*",
"industryCode": industryCode,
"beginTime": beginDate,
"endTime": endDate,
"pageNo": "1",
"pageSize": "50",
"p": "1",
"pageNum": "1",
"pageNumber": "1",
"qType": "1",
}
url := "https://reportapi.eastmoney.com/report/list"
logger.SugaredLogger.Infof("beginDate:%s endDate:%s", beginDate, endDate)
resp, err := resty.New().SetTimeout(time.Duration(15)*time.Second).R().
SetHeader("Host", "reportapi.eastmoney.com").
SetHeader("Origin", "https://data.eastmoney.com").
SetHeader("Referer", "https://data.eastmoney.com/report/stock.jshtml").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
SetHeader("Content-Type", "application/json").
SetQueryParams(params).Get(url)
respMap := map[string]any{}
if err != nil {
return []any{}
}
json.Unmarshal(resp.Body(), &respMap)
//logger.SugaredLogger.Infof("resp:%+v", respMap["data"])
return respMap["data"].([]any)
}
func (m MarketNewsApi) StockResearchReport(stockCode string, days int) []any {
beginDate := time.Now().Add(-time.Duration(days) * 24 * time.Hour).Format("2006-01-02")
endDate := time.Now().Format("2006-01-02")
if strutil.ContainsAny(stockCode, []string{"."}) {
stockCode = strings.Split(stockCode, ".")[0]
beginDate = time.Now().Add(-time.Duration(days) * 365 * time.Hour).Format("2006-01-02")
} else {
stockCode = strutil.ReplaceWithMap(stockCode, map[string]string{
"sh": "",
"sz": "",
"gb_": "",
"us": "",
"us_": "",
})
beginDate = time.Now().Add(-time.Duration(days) * 365 * time.Hour).Format("2006-01-02")
}
logger.SugaredLogger.Infof("StockResearchReport-stockCode:%s", stockCode)
type Req struct {
BeginTime string `json:"beginTime"`
EndTime string `json:"endTime"`
IndustryCode string `json:"industryCode"`
RatingChange string `json:"ratingChange"`
Rating string `json:"rating"`
OrgCode interface{} `json:"orgCode"`
Code string `json:"code"`
Rcode string `json:"rcode"`
PageSize int `json:"pageSize"`
PageNo int `json:"pageNo"`
P int `json:"p"`
PageNum int `json:"pageNum"`
PageNumber int `json:"pageNumber"`
}
url := "https://reportapi.eastmoney.com/report/list2"
logger.SugaredLogger.Infof("beginDate:%s endDate:%s", beginDate, endDate)
resp, err := resty.New().SetTimeout(time.Duration(15)*time.Second).R().
SetHeader("Host", "reportapi.eastmoney.com").
SetHeader("Origin", "https://data.eastmoney.com").
SetHeader("Referer", "https://data.eastmoney.com/report/stock.jshtml").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
SetHeader("Content-Type", "application/json").
SetBody(&Req{
Code: stockCode,
IndustryCode: "*",
BeginTime: beginDate,
EndTime: endDate,
PageNo: 1,
PageSize: 50,
P: 1,
PageNum: 1,
PageNumber: 1,
}).Post(url)
respMap := map[string]any{}
if err != nil {
return []any{}
}
json.Unmarshal(resp.Body(), &respMap)
//logger.SugaredLogger.Infof("resp:%+v", respMap["data"])
return respMap["data"].([]any)
}
func (m MarketNewsApi) StockNotice(stock_list string) []any {
var stockCodes []string
for _, stockCode := range strings.Split(stock_list, ",") {
if strutil.ContainsAny(stockCode, []string{"."}) {
stockCode = strings.Split(stockCode, ".")[0]
stockCodes = append(stockCodes, stockCode)
} else {
stockCode = strutil.ReplaceWithMap(stockCode, map[string]string{
"sh": "",
"sz": "",
"gb_": "",
"us": "",
"us_": "",
})
stockCodes = append(stockCodes, stockCode)
}
}
url := "https://np-anotice-stock.eastmoney.com/api/security/ann?page_size=50&page_index=1&ann_type=SHA%2CCYB%2CSZA%2CBJA%2CINV&client_source=web&f_node=0&stock_list=" + strings.Join(stockCodes, ",")
resp, err := resty.New().SetTimeout(time.Duration(15)*time.Second).R().
SetHeader("Host", "np-anotice-stock.eastmoney.com").
SetHeader("Referer", "https://data.eastmoney.com/notices/hsa/5.html").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
Get(url)
respMap := map[string]any{}
if err != nil {
return []any{}
}
json.Unmarshal(resp.Body(), &respMap)
//logger.SugaredLogger.Infof("resp:%+v", respMap["data"])
return (respMap["data"].(map[string]any))["list"].([]any)
}
func (m MarketNewsApi) EMDictCode(code string, cache *freecache.Cache) []any {
respMap := map[string]any{}
d, _ := cache.Get([]byte(code))
if d != nil {
json.Unmarshal(d, &respMap)
return respMap["data"].([]any)
}
url := "https://reportapi.eastmoney.com/report/bk"
params := map[string]string{
"bkCode": code,
}
resp, err := resty.New().SetTimeout(time.Duration(15)*time.Second).R().
SetHeader("Host", "reportapi.eastmoney.com").
SetHeader("Origin", "https://data.eastmoney.com").
SetHeader("Referer", "https://data.eastmoney.com/report/industry.jshtml").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
SetHeader("Content-Type", "application/json").
SetQueryParams(params).Get(url)
if err != nil {
return []any{}
}
json.Unmarshal(resp.Body(), &respMap)
//logger.SugaredLogger.Infof("resp:%+v", respMap["data"])
cache.Set([]byte(code), resp.Body(), 60*60*24)
return respMap["data"].([]any)
}
func (m MarketNewsApi) TradingViewNews() *[]models.TVNews {
TVNews := &[]models.TVNews{}
url := "https://news-mediator.tradingview.com/news-flow/v2/news?filter=lang:zh-Hans&filter=provider:panews,reuters&client=screener&streaming=false"
resp, err := resty.New().SetProxy("http://127.0.0.1:10809").SetTimeout(time.Duration(30)*time.Second).R().
SetHeader("Host", "news-mediator.tradingview.com").
SetHeader("Origin", "https://cn.tradingview.com").
SetHeader("Referer", "https://cn.tradingview.com/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
Get(url)
if err != nil {
logger.SugaredLogger.Errorf("TradingViewNews err:%s", err.Error())
return TVNews
}
respMap := map[string]any{}
err = json.Unmarshal(resp.Body(), &respMap)
if err != nil {
return TVNews
}
items, err := json.Marshal(respMap["items"])
if err != nil {
return TVNews
}
json.Unmarshal(items, TVNews)
return TVNews
}
func (m MarketNewsApi) XUEQIUHotStock(size int, marketType string) *[]models.HotItem {
request := resty.New().SetTimeout(time.Duration(30) * time.Second).R()
_, err := request.
SetHeader("Host", "xueqiu.com").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
Get("https://xueqiu.com/hq#hot")
//cookies := resp.Header().Get("Set-Cookie")
//logger.SugaredLogger.Infof("cookies:%s", cookies)
url := fmt.Sprintf("https://stock.xueqiu.com/v5/stock/hot_stock/list.json?page=1&size=%d&_type=%s&type=%s", size, marketType, marketType)
res := &models.XUEQIUHot{}
_, err = request.
SetHeader("Host", "stock.xueqiu.com").
SetHeader("Origin", "https://xueqiu.com").
SetHeader("Referer", "https://xueqiu.com/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
//SetHeader("Cookie", "cookiesu=871730774144180; device_id=ee75cebba8a35005c9e7baf7b7dead59; s=ch12b12pfi; Hm_lvt_1db88642e346389874251b5a1eded6e3=1746247619; xq_a_token=361dcfccb1d32a1d9b5b65f1a188b9c9ed1e687d; xqat=361dcfccb1d32a1d9b5b65f1a188b9c9ed1e687d; xq_r_token=450d1db0db9659a6af7cc9297bfa4fccf1776fae; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOi0xLCJpc3MiOiJ1YyIsImV4cCI6MTc1MzgzODAwNiwiY3RtIjoxNzUxMjUxMzc2MDY3LCJjaWQiOiJkOWQwbjRBWnVwIn0.TjEtQ5WEN4ajnVjVnY3J-Qq9LjL-F0eat9Cefv_tLJLqsPhzD2y8Lc1CeIu0Ceqhlad7O_yW1tR9nb2dIjDpyOPzWKxvwSOKXLm8XMoz4LMgE2pysBCH4TsetzHsEOhBsY467q-JX3WoFuqo-dqv1FfLSondZCspjEMFdgPFt2V-2iXJY05YUwcBVUvL74mT9ZjNq0KaDeRBJk_il6UR8yibG7RMbe9xWYz5dSO_wJwWuxvnZ8u9EXC2m-TV7-QHVxFHR_5e8Fodrzg0yIcLU4wBTSoIIQDUKqngajX2W-nUAdo6fr78NNDmoswFVH7T7XMuQciMAqj9MpMCVW3Sog; u=871730774144180; ssxmod_itna=iq+h7KAImDORKYQ4Y5G=nxBKDtD7D3qCD0dGMDxeq7tDRDFqApKDHtA68oon7ziBA0+PbZ9xGN4oYxiNDAPq0iDC+Wjxs9Orw5KQb9iqP4MAn0TbNsbtU22eqbCe=S3vTv6xoDHxY=DU1GzeieDx=PD5xDTDWeDGDD3DmnsDi5YD0KDjBYpH+omDYPDEBYDaxDbDimwY4GCrDDCtc5Dw6bmzDDzznL5WWAPzWffZg3YcFgxf8GwD7y3Dla4rMhw23=cz0Efdk0A5hYDXotDvhoY1/H6neEvOt3o=Q0ruT+5RuxoRhDxCmh5tGP32xBD5G0xS2xcb4quDK0Dy2ZmY/DDWM0qmEeSEDeOCIq1fw1misCY=WAzoOtMwDzGdUjpRk5Z0xQBDI2IMw4H7qNiNBLxWiDD; ssxmod_itna2=iq+h7KAImDORKYQ4Y5G=nxBKDtD7D3qCD0dGMDxeq7tDRDFqApKDHtA68oon7ziBA0+PbZYxD3boBmiEPtDFOEPAeFmDDsuGSxf46oGKwGHd8wtUjFe+oV1lxUzutkGly=nCyCjq=UTHxMxFCr1DsFiKPuEpPVO7GrOyk5Aymnc0+11AFND7v16PvwrFQH4I72=3O1OpK7rGw+poWNCxjj=Ka5QDFWAvEzrDFQcIH=GpKpS90FAyIzGcTyck+yhQKaojn96dRqeIh=HkaFrlGnKwzO+a49=F7/c/MejoR3QM20K9IIOymrMN2bsk2TRdKFiaf4O0ut2MauiOER=iQNW2WVgDrkKzD=57r577wEx2hwkqhf8T8BDvkHZRDirC0bNK4O=G3TSkd3wYwq8bst0t9qF/e3M87NYtU2IWYWzqd=BqEfdqGq0R8wxmqLzpeGeuwSTq1OAiB87gDrozjnGkwDKRdrLz8uDjQKVlGhWk8Wd/rXQjx4pG=BNqpW/6TS1wpfxzGf5CrUhtt0j0wC5AUFo2GbX+QXPzD2guxKXrx8lZUQlwWIHyEUz+OLh0eWUkfHfM0YWXlgOejnuUa06rW9y5maDPipGms751hxKcqLq62pQty4iX3QDF6SRQd3tfEBf3CH7r2xe2qq0qdOI5Ge=GezD/Us5Z0xQBwVAZ2N/XvD0HDD").
SetResult(res).
Get(url)
if err != nil {
logger.SugaredLogger.Errorf("XUEQIUHotStock err:%s", err.Error())
return &[]models.HotItem{}
}
logger.SugaredLogger.Infof("XUEQIUHotStock:%+v", res)
return &res.Data.Items
}
func (m MarketNewsApi) HotEvent(size int) *[]models.HotEvent {
events := &[]models.HotEvent{}
url := fmt.Sprintf("https://xueqiu.com/hot_event/list.json?count=%d", size)
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
SetHeader("Host", "xueqiu.com").
SetHeader("Origin", "https://xueqiu.com").
SetHeader("Referer", "https://xueqiu.com/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
SetHeader("Cookie", "cookiesu=2617378771242871; s=c2121pp1u71; device_id=237a58584ec58d8e4d4e1040700a644f1; Hm_lvt_1db88642e346389874251b5a1eded6e3=1744100219,1744599115; xq_a_token=b7259d09435458cc3f1a963479abb270a1a016ce; xqat=b7259d09435458cc3f1a963479abb270a1a016ce; xq_r_token=28108bfa1d92ac8a46bbb57722633746218621a3; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOi0xLCJpc3MiOiJ1YyIsImV4cCI6MTc1MjU0MTk4OCwiY3RtIjoxNzUwMjMwNjA2NzI0LCJjaWQiOiJkOWQwbjRBWnVwIn0.kU_fz0luJoE7nr-K4UrNUi5-mAG-vMdXtuC4mUKIppILId4UpF70LB70yunxGiNSw6tPFR3-hyLvztKAHtekCUTm3XjUl5b3tEDP-ZUVqHnWXO_5hoeMI8h-Cfx6ZGlIr5x3icvTPkT0OV5CD5A33-ZDTKhKPf-DhJ_-m7CG5GbX4MseOBeMXuLUQUiYHPKhX1QUc0GTGrCzi8Mki0z49D0LVqCSgbsx3UGfowOOyx85_cXb4OAFvIjwbs2p0o_h-ibIT0ngVkkAyEDetVvlcZ_bkardhseCB7k9BEMgH2z8ihgkVxyy3P0degLmDUruhmqn5uZOCi1pVBDvCv9lBg; u=261737877124287; ssxmod_itna=QuG=D5AKiKDIqCqGKi7G7DgmmPlSDWFqKGHDyx4YK0CDmxjKiddDUQivnb8xpnQcGyGYoYhoqEeDBubrDSxD67DK4GTm+ogiw1o3B=xedQHDgBtN=7/i1K53N+rOjquLMU=kbqYxB3DExGkqj0tPi4DxaPD5xDTDWeDGDD3DnnsDQKDRx0kL0oDIxD1D0bmHUEvh38mDYePLmOmDYPYx94Y8KoDeEgsD7HUl/vIGGEAqjLPFegXLD0HolCqr4DCid1qDm+ECfkjDn9sD0KP8fn+CRoDv=tYr4ibx+o=W+8vstf9mjGe3cXseWdBmoFrmf4DA3bFAxnAxD7vYxADaDoerDGHPoxHF+PKGPtDKmiqQGeB5qbi4eg4KDHKDe3DeG0qeEP9xVUoHDDWMYYM0ICr4FBimBDM7D0x4QOECmhul5QCN/m5/74lGm=7x9Wp7A+i7xQ7wlMD4D; ssxmod_itna2=QuG=D5AKiKDIqCqGKi7G7DgmmPlSDWFqKGHDyx4YK0CDmxjKiddDUQivnb8xpnQcGyGYoYhoqoDirSDhPmGD24GajjDuGE3m7or4DlxOSGewHl6iaus2Q62SRX5CFjCds6ltF9xy6iaUuB262UkhRA8UXST=4/b+y3kGKzlGE8T29FA008ljy9jXXC7f7m7QsK667mlUooWrofk=qGZjxtcUrN1NtuAnne1hj+rQP5UnlFkxf+o7VjmatH7u7bCDlbTt3cz6CH9Fl4vye16W/ellc8I3Q37W7ZwiLGD/zPpZcnd2nsqqo/+zRbKAmz4plzwaDqGUe7f9E+P0IFRKqpRv+buQFHBSpcbwND7Q+9XWmnjI2UwKd98jIS3gPXwxvbx4OuiyH8gZ+OEt7DgE/AY/9W4VxDZrlFWyWnC4y4/I0IpAfaGKpbPmauKbkqawqv93vSf+9HamGe0Dt2PNgT3yiEB4vQP2/DdVpcGBOjFujWoHP32OshLPYI20LRCKddwEGkKqPzPwKPc3X5zuB=w2fUdtwKsAW5kQtsl8clNwjC5uDYrxR0h9xaj0xmD+YuI3GPT7xYTalRImPj2wL2=+91a304xa4bTWtP=dLGARhb/efRi0uktaz8i8C04G0x/ZWUzqRza8GGU=FfRfvb4GZM/q2rVsl0nLvRjGeAKgocLouyXs/uwZu3YxbAx30qCbjG1A533zAxIeIgD=0VAc3ixD").
Get(url)
if err != nil {
logger.SugaredLogger.Errorf("HotEvent err:%s", err.Error())
return events
}
//logger.SugaredLogger.Infof("HotEvent:%s", resp.Body())
respMap := map[string]any{}
err = json.Unmarshal(resp.Body(), &respMap)
items, err := json.Marshal(respMap["list"])
if err != nil {
return events
}
json.Unmarshal(items, events)
return events
}
func (m MarketNewsApi) HotTopic(size int) []any {
url := "https://gubatopic.eastmoney.com/interface/GetData.aspx?path=newtopic/api/Topic/HomePageListRead"
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
SetHeader("Host", "gubatopic.eastmoney.com").
SetHeader("Origin", "https://gubatopic.eastmoney.com").
SetHeader("Referer", "https://gubatopic.eastmoney.com/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
SetFormData(map[string]string{
"param": fmt.Sprintf("ps=%d&p=1&type=0", size),
"path": "newtopic/api/Topic/HomePageListRead",
"env": "2",
}).
Post(url)
if err != nil {
logger.SugaredLogger.Errorf("HotTopic err:%s", err.Error())
return []any{}
}
//logger.SugaredLogger.Infof("HotTopic:%s", resp.Body())
respMap := map[string]any{}
err = json.Unmarshal(resp.Body(), &respMap)
return respMap["re"].([]any)
}
func (m MarketNewsApi) InvestCalendar(yearMonth string) []any {
if yearMonth == "" {
yearMonth = time.Now().Format("2006-01")
}
url := "https://app.jiuyangongshe.com/jystock-app/api/v1/timeline/list"
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
SetHeader("Host", "app.jiuyangongshe.com").
SetHeader("Origin", "https://www.jiuyangongshe.com").
SetHeader("Referer", "https://www.jiuyangongshe.com/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
SetHeader("Content-Type", "application/json").
SetHeader("token", "1cc6380a05c652b922b3d85124c85473").
SetHeader("platform", "3").
SetHeader("Cookie", "SESSION=NDZkNDU2ODYtODEwYi00ZGZkLWEyY2ItNjgxYzY4ZWMzZDEy").
SetHeader("timestamp", strconv.FormatInt(time.Now().UnixMilli(), 10)).
SetBody(map[string]string{
"date": yearMonth,
"grade": "0",
}).
Post(url)
if err != nil {
logger.SugaredLogger.Errorf("InvestCalendar err:%s", err.Error())
return []any{}
}
//logger.SugaredLogger.Infof("InvestCalendar:%s", resp.Body())
respMap := map[string]any{}
err = json.Unmarshal(resp.Body(), &respMap)
return respMap["data"].([]any)
}
func (m MarketNewsApi) ClsCalendar() []any {
url := "https://www.cls.cn/api/calendar/web/list?app=CailianpressWeb&flag=0&os=web&sv=8.4.6&type=0&sign=4b839750dc2f6b803d1c8ca00d2b40be"
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
SetHeader("Host", "www.cls.cn").
SetHeader("Origin", "https://www.cls.cn").
SetHeader("Referer", "https://www.cls.cn/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
Get(url)
if err != nil {
logger.SugaredLogger.Errorf("ClsCalendar err:%s", err.Error())
return []any{}
}
respMap := map[string]any{}
err = json.Unmarshal(resp.Body(), &respMap)
return respMap["data"].([]any)
}

View File

@ -2,7 +2,6 @@ package data
import ( import (
"encoding/json" "encoding/json"
"github.com/coocood/freecache"
"go-stock/backend/db" "go-stock/backend/db"
"go-stock/backend/logger" "go-stock/backend/logger"
"testing" "testing"
@ -28,126 +27,3 @@ func TestGlobalStockIndexes(t *testing.T) {
} }
logger.SugaredLogger.Debugf("resp: %+v", string(bytes)) logger.SugaredLogger.Debugf("resp: %+v", string(bytes))
} }
func TestGetIndustryRank(t *testing.T) {
res := NewMarketNewsApi().GetIndustryRank("0", 10)
for s, a := range res["data"].([]any) {
logger.SugaredLogger.Debugf("key: %+v, value: %+v", s, a)
}
}
func TestGetIndustryMoneyRankSina(t *testing.T) {
res := NewMarketNewsApi().GetIndustryMoneyRankSina("0", "netamount")
for i, re := range res {
logger.SugaredLogger.Debugf("key: %+v, value: %+v", i, re)
}
}
func TestGetMoneyRankSina(t *testing.T) {
res := NewMarketNewsApi().GetMoneyRankSina("r3_net")
for i, re := range res {
logger.SugaredLogger.Debugf("key: %+v, value: %+v", i, re)
}
}
func TestGetStockMoneyTrendByDay(t *testing.T) {
res := NewMarketNewsApi().GetStockMoneyTrendByDay("sh600438", 360)
for i, re := range res {
logger.SugaredLogger.Debugf("key: %+v, value: %+v", i, re)
}
}
func TestTopStocksRankingList(t *testing.T) {
NewMarketNewsApi().TopStocksRankingList("2025-05-19")
}
func TestLongTiger(t *testing.T) {
db.Init("../../data/stock.db")
NewMarketNewsApi().LongTiger("2025-06-08")
}
func TestStockResearchReport(t *testing.T) {
db.Init("../../data/stock.db")
resp := NewMarketNewsApi().StockResearchReport("600584.sh", 7)
for _, a := range resp {
logger.SugaredLogger.Debugf("value: %+v", a)
}
}
func TestIndustryResearchReport(t *testing.T) {
db.Init("../../data/stock.db")
resp := NewMarketNewsApi().IndustryResearchReport("", 7)
for _, a := range resp {
logger.SugaredLogger.Debugf("value: %+v", a)
}
}
func TestStockNotice(t *testing.T) {
db.Init("../../data/stock.db")
resp := NewMarketNewsApi().StockNotice("600584,600900")
for _, a := range resp {
logger.SugaredLogger.Debugf("value: %+v", a)
}
}
func TestEMDictCode(t *testing.T) {
db.Init("../../data/stock.db")
resp := NewMarketNewsApi().EMDictCode("016", freecache.NewCache(100))
for _, a := range resp {
logger.SugaredLogger.Debugf("value: %+v", a)
}
}
func TestTradingViewNews(t *testing.T) {
db.Init("../../data/stock.db")
resp := NewMarketNewsApi().TradingViewNews()
for _, a := range *resp {
logger.SugaredLogger.Debugf("value: %+v", a)
}
}
func TestXUEQIUHotStock(t *testing.T) {
db.Init("../../data/stock.db")
res := NewMarketNewsApi().XUEQIUHotStock(50, "10")
for _, a := range *res {
logger.SugaredLogger.Debugf("value: %+v", a)
}
}
func TestHotEvent(t *testing.T) {
db.Init("../../data/stock.db")
res := NewMarketNewsApi().HotEvent(50)
for _, a := range *res {
logger.SugaredLogger.Debugf("value: %+v", a)
}
}
func TestHotTopic(t *testing.T) {
db.Init("../../data/stock.db")
res := NewMarketNewsApi().HotTopic(10)
for _, a := range res {
logger.SugaredLogger.Debugf("value: %+v", a)
}
}
func TestInvestCalendar(t *testing.T) {
db.Init("../../data/stock.db")
res := NewMarketNewsApi().InvestCalendar("2025-06")
for _, a := range res {
logger.SugaredLogger.Debugf("value: %+v", a)
}
}
func TestClsCalendar(t *testing.T) {
db.Init("../../data/stock.db")
res := NewMarketNewsApi().ClsCalendar()
for _, a := range res {
logger.SugaredLogger.Debugf("value: %+v", a)
}
}

View File

@ -161,7 +161,7 @@ func (o OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int)
}() }()
wg.Wait() wg.Wait()
news := NewMarketNewsApi().GetNewsList("", 100) news := NewMarketNewsApi().GetNewsList("财联社电报", 100)
messageText := strings.Builder{} messageText := strings.Builder{}
for _, telegraph := range *news { for _, telegraph := range *news {
messageText.WriteString("## " + telegraph.Time + ":" + "\n") messageText.WriteString("## " + telegraph.Time + ":" + "\n")
@ -849,6 +849,6 @@ func (o OpenAi) SaveAIResponseResult(stockCode, stockName, result, chatId, quest
func (o OpenAi) GetAIResponseResult(stock string) *models.AIResponseResult { func (o OpenAi) GetAIResponseResult(stock string) *models.AIResponseResult {
var result models.AIResponseResult var result models.AIResponseResult
db.Dao.Where("stock_code = ?", stock).Order("id desc").Limit(1).Find(&result) db.Dao.Where("stock_code = ?", stock).Order("id desc").Limit(1).First(&result)
return &result return &result
} }

View File

@ -19,12 +19,11 @@ func TestNewDeepSeekOpenAiConfig(t *testing.T) {
} }
func TestGetTopNewsList(t *testing.T) { func TestGetTopNewsList(t *testing.T) {
news := GetTopNewsList(30) GetTopNewsList(30)
t.Log(news)
} }
func TestSearchGuShiTongStockInfo(t *testing.T) { func TestSearchGuShiTongStockInfo(t *testing.T) {
db.Init("../../data/stock.db") //db.Init("../../data/stock.db")
SearchGuShiTongStockInfo("hk01810", 60) SearchGuShiTongStockInfo("hk01810", 60)
SearchGuShiTongStockInfo("sh600745", 60) SearchGuShiTongStockInfo("sh600745", 60)
SearchGuShiTongStockInfo("gb_goog", 60) SearchGuShiTongStockInfo("gb_goog", 60)

View File

@ -1,55 +0,0 @@
package data
import (
"encoding/json"
"fmt"
"github.com/go-resty/resty/v2"
"go-stock/backend/logger"
"time"
)
// @Author spark
// @Date 2025/6/28 21:02
// @Desc
// -----------------------------------------------------------------------------------
type SearchStockApi struct {
words string
}
func NewSearchStockApi(words string) *SearchStockApi {
return &SearchStockApi{words: words}
}
func (s SearchStockApi) SearchStock() map[string]any {
url := "https://np-tjxg-g.eastmoney.com/api/smart-tag/stock/v3/pw/search-code"
resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R().
SetHeader("Host", "np-tjxg-g.eastmoney.com").
SetHeader("Origin", "https://xuangu.eastmoney.com").
SetHeader("Referer", "https://xuangu.eastmoney.com/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
SetHeader("Content-Type", "application/json").
SetBody(fmt.Sprintf(`{
"keyWord": "%s",
"pageSize": 50000,
"pageNo": 1,
"fingerprint": "e38b5faabf9378c8238e57219f0ebc9b",
"gids": [],
"matchWord": "",
"timestamp": "1751113883290349",
"shareToGuba": false,
"requestId": "8xTWgCDAjvQ5lmvz5mDA3Ydk2AE4yoiJ1751113883290",
"needCorrect": true,
"removedConditionIdList": [],
"xcId": "xc0af28549ab330013ed",
"ownSelectAll": false,
"dxInfo": [],
"extraCondition": ""
}`, s.words)).Post(url)
if err != nil {
logger.SugaredLogger.Errorf("SearchStock-err:%+v", err)
return map[string]any{}
}
respMap := map[string]any{}
json.Unmarshal(resp.Body(), &respMap)
//logger.SugaredLogger.Infof("resp:%+v", respMap["data"])
return respMap
}

View File

@ -1,25 +0,0 @@
package data
import (
"go-stock/backend/db"
"go-stock/backend/logger"
"testing"
)
func TestSearchStock(t *testing.T) {
db.Init("../../data/stock.db")
res := NewSearchStockApi("算力股;净利润连续3年增长").SearchStock()
data := res["data"].(map[string]any)
result := data["result"].(map[string]any)
dataList := result["dataList"].([]any)
for _, v := range dataList {
d := v.(map[string]any)
logger.SugaredLogger.Infof("%s:%s", d["INDUSTRY"], d["SECURITY_SHORT_NAME"])
}
//columns := result["columns"].([]any)
//for _, v := range columns {
// logger.SugaredLogger.Infof("v:%+v", v)
//}
}

View File

@ -34,7 +34,6 @@ type Settings struct {
DarkTheme bool `json:"darkTheme"` DarkTheme bool `json:"darkTheme"`
BrowserPoolSize int `json:"browserPoolSize"` BrowserPoolSize int `json:"browserPoolSize"`
EnableFund bool `json:"enableFund"` EnableFund bool `json:"enableFund"`
EnablePushNews bool `json:"enablePushNews"`
} }
func (receiver Settings) TableName() string { func (receiver Settings) TableName() string {
@ -79,7 +78,6 @@ func (s SettingsApi) UpdateConfig() string {
"enable_news": s.Config.EnableNews, "enable_news": s.Config.EnableNews,
"dark_theme": s.Config.DarkTheme, "dark_theme": s.Config.DarkTheme,
"enable_fund": s.Config.EnableFund, "enable_fund": s.Config.EnableFund,
"enable_push_news": s.Config.EnablePushNews,
}) })
} else { } else {
logger.SugaredLogger.Infof("未找到配置,创建默认配置:%+v", s.Config) logger.SugaredLogger.Infof("未找到配置,创建默认配置:%+v", s.Config)
@ -107,7 +105,6 @@ func (s SettingsApi) UpdateConfig() string {
EnableNews: s.Config.EnableNews, EnableNews: s.Config.EnableNews,
DarkTheme: s.Config.DarkTheme, DarkTheme: s.Config.DarkTheme,
EnableFund: s.Config.EnableFund, EnableFund: s.Config.EnableFund,
EnablePushNews: s.Config.EnablePushNews,
}) })
} }
return "保存成功!" return "保存成功!"

File diff suppressed because one or more lines are too long

View File

@ -8,6 +8,7 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/chromedp/chromedp" "github.com/chromedp/chromedp"
@ -15,7 +16,6 @@ import (
"github.com/duke-git/lancet/v2/slice" "github.com/duke-git/lancet/v2/slice"
"github.com/duke-git/lancet/v2/strutil" "github.com/duke-git/lancet/v2/strutil"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/robertkrimen/otto"
"github.com/samber/lo" "github.com/samber/lo"
"go-stock/backend/db" "go-stock/backend/db"
"go-stock/backend/logger" "go-stock/backend/logger"
@ -26,14 +26,11 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/plugin/soft_delete" "gorm.io/plugin/soft_delete"
"io" "io"
"io/ioutil"
"strings" "strings"
"time" "time"
) )
const sinaStockUrl = "http://hq.sinajs.cn/rn=%d&list=%s" const sinaStockUrl = "http://hq.sinajs.cn/rn=%d&list=%s"
const txStockUrl = "http://qt.gtimg.cn/?_=%d&q=%s"
const tushareApiUrl = "http://api.tushare.pro" const tushareApiUrl = "http://api.tushare.pro"
type StockDataApi struct { type StockDataApi struct {
@ -248,7 +245,7 @@ func (receiver StockDataApi) GetIndexBasic() {
func (receiver StockDataApi) GetStockBaseInfo() { func (receiver StockDataApi) GetStockBaseInfo() {
res := &TushareStockBasicResponse{} res := &TushareStockBasicResponse{}
fields := "ts_code,symbol,name,area,industry,cnspell,market,list_date,act_name,act_ent_type,fullname,exchange,list_status,curr_type,enname,delist_date,is_hs" fields := "ts_code,symbol,name,area,industry,cnspell,market,list_date,act_name,act_ent_type,fullname,exchange,list_status,curr_type,enname,delist_date,is_hs"
resp, err := receiver.client.R(). _, err := receiver.client.R().
SetHeader("content-type", "application/json"). SetHeader("content-type", "application/json").
SetBody(&TushareRequest{ SetBody(&TushareRequest{
ApiName: "stock_basic", ApiName: "stock_basic",
@ -259,7 +256,8 @@ func (receiver StockDataApi) GetStockBaseInfo() {
SetResult(res). SetResult(res).
Post(tushareApiUrl) Post(tushareApiUrl)
//logger.SugaredLogger.Infof("GetStockBaseInfo %s", string(resp.Body())) //logger.SugaredLogger.Infof("GetStockBaseInfo %s", string(resp.Body()))
ioutil.WriteFile("stock_basic.json", resp.Body(), 0666) //resp.Body()写入文件
//ioutil.WriteFile("stock_basic.json", resp.Body(), 0666)
//logger.SugaredLogger.Infof("GetStockBaseInfo %+v", res) //logger.SugaredLogger.Infof("GetStockBaseInfo %+v", res)
if err != nil { if err != nil {
logger.SugaredLogger.Error(err.Error()) logger.SugaredLogger.Error(err.Error())
@ -292,58 +290,8 @@ func (receiver StockDataApi) GetStockBaseInfo() {
} }
func (receiver StockDataApi) GetStockCodeRealTimeData(StockCodes ...string) (*[]StockInfo, error) { func (receiver StockDataApi) GetStockCodeRealTimeData(StockCodes ...string) (*[]StockInfo, error) {
stockInfos := make([]StockInfo, 0)
hkcodes := slice.Filter(StockCodes, func(i int, s string) bool { codes := slice.JoinFunc(StockCodes, ",", func(s string) string {
return strutil.HasPrefixAny(s, []string{"hk", "HK", "sh", "sz"})
})
if hkcodes != nil && len(hkcodes) > 0 {
hkcodesStr := slice.JoinFunc(hkcodes, ",", func(s string) string {
if strutil.HasPrefixAny(s, []string{"hk", "HK"}) {
return "r_" + strings.ToLower(s)
} else {
return strings.ToLower(s)
}
})
url := fmt.Sprintf(txStockUrl, time.Now().Unix(), hkcodesStr)
resp, err := receiver.client.R().
SetHeader("Host", "qt.gtimg.cn").
SetHeader("Referer", "https://gu.qq.com/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
Get(url)
logger.SugaredLogger.Infof("GetStockCodeRealTimeData %s", url)
if err != nil {
logger.SugaredLogger.Error(err.Error())
return &[]StockInfo{}, err
}
str := GB18030ToUTF8(resp.Body())
dataStr := strutil.SplitAndTrim(strings.Trim(str, "\n"), ";")
for _, data := range dataStr {
stockData, err := ParseTxStockData(data)
if err != nil {
logger.SugaredLogger.Error(err.Error())
continue
}
stockInfos = append(stockInfos, *stockData)
go func() {
var count int64
db.Dao.Model(&StockInfo{}).Where("code = ?", stockData.Code).Count(&count)
if count == 0 {
db.Dao.Model(&StockInfo{}).Create(stockData)
} else {
db.Dao.Model(&StockInfo{}).Where("code = ?", stockData.Code).Updates(stockData)
}
}()
}
}
szzsusCodes := slice.Filter(StockCodes, func(i int, s string) bool {
return !strutil.HasPrefixAny(s, []string{"hk", "HK", "sh", "sz"})
})
codes := slice.JoinFunc(szzsusCodes, ",", func(s string) string {
if strings.HasPrefix(s, "us") { if strings.HasPrefix(s, "us") {
s = strings.Replace(s, "us", "gb_", 1) s = strings.Replace(s, "us", "gb_", 1)
} }
@ -365,9 +313,12 @@ func (receiver StockDataApi) GetStockCodeRealTimeData(StockCodes ...string) (*[]
return &[]StockInfo{}, err return &[]StockInfo{}, err
} }
stockInfos := make([]StockInfo, 0)
str := GB18030ToUTF8(resp.Body()) str := GB18030ToUTF8(resp.Body())
dataStr := strutil.SplitEx(str, "\n", true) dataStr := strutil.SplitEx(str, "\n", true)
if len(dataStr) == 0 {
return &[]StockInfo{}, errors.New("获取股票信息失败,请检查股票代码是否正确")
}
for _, data := range dataStr { for _, data := range dataStr {
//logger.SugaredLogger.Info(data) //logger.SugaredLogger.Info(data)
stockData, err := ParseFullSingleStockData(data) stockData, err := ParseFullSingleStockData(data)
@ -400,20 +351,7 @@ func (receiver StockDataApi) Follow(stockCode string) string {
logger.SugaredLogger.Error(err) logger.SugaredLogger.Error(err)
return "关注失败" return "关注失败"
} }
if strings.HasPrefix(stockCode, "us") {
stockCode = strings.Replace(stockCode, "us", "gb_", 1)
}
if strings.HasPrefix(stockCode, "US") {
stockCode = strings.Replace(stockCode, "US", "gb_", 1)
}
count := int64(0)
db.Dao.Model(&FollowedStock{}).Where("is_del = ?", 0).Count(&count)
logger.SugaredLogger.Errorf("Follow-count %v", count)
if count >= 63 {
return "最多只能关注63只股票"
}
stockCode = strings.ToLower(stockCode)
maxSort := int64(0) maxSort := int64(0)
db.Dao.Model(&FollowedStock{}).Raw("select max(sort) as sort from followed_stock").Scan(&maxSort) db.Dao.Model(&FollowedStock{}).Raw("select max(sort) as sort from followed_stock").Scan(&maxSort)
@ -476,64 +414,15 @@ func (receiver StockDataApi) SetAlarmChangePercent(val, alarmPrice float64, stoc
return "设置成功" return "设置成功"
} }
func (receiver StockDataApi) SetStockSort(newSort int64, stockCode string) { func (receiver StockDataApi) SetStockSort(sort int64, stockCode string) {
//if strutil.HasPrefixAny(stockCode, []string{"gb_"}) { if strutil.HasPrefixAny(stockCode, []string{"gb_"}) {
// stockCode = strings.ToLower(stockCode) stockCode = strings.ToLower(stockCode)
// stockCode = strings.Replace(stockCode, "gb_", "us", 1) stockCode = strings.Replace(stockCode, "gb_", "us", 1)
//}
// 获取当前排序值
var currentStock FollowedStock
if err := db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", strings.ToLower(stockCode)).First(&currentStock).Error; err != nil {
logger.SugaredLogger.Error("找不到当前股票: ", err.Error())
return
} }
err := db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", strings.ToLower(stockCode)).Update("sort", sort).Error
oldSort := currentStock.Sort if err != nil {
logger.SugaredLogger.Error(err.Error())
// 如果排序值没有变化,直接返回
if oldSort == newSort {
return
} }
// 检查新排序位置是否被占用
var count int64
if err := db.Dao.Model(&FollowedStock{}).Where("sort = ?", newSort).Count(&count).Error; err != nil {
logger.SugaredLogger.Error("检查新排序位置被占用失败: ", err.Error())
return
}
if count == 0 {
// 新位置未被占用,直接更新当前记录
if err := db.Dao.Model(&FollowedStock{}).
Where("stock_code = ?", strings.ToLower(stockCode)).
Update("sort", newSort).Error; err != nil {
logger.SugaredLogger.Error("更新排序位置失败: ", err.Error())
}
} else {
// 新位置已被占用,需要移动其他记录
if newSort < oldSort {
// 向前移动:将中间记录向后移动
if err := db.Dao.Model(&FollowedStock{}).
Where("sort >= ? AND sort < ?", newSort, oldSort).
Update("sort", gorm.Expr("sort + 1")).Error; err != nil {
logger.SugaredLogger.Error("向前排序更新失败: ", err.Error())
}
} else {
// 向后移动:将中间记录向前移动
if err := db.Dao.Model(&FollowedStock{}).
Where("sort > ? AND sort <= ?", oldSort, newSort).
Update("sort", gorm.Expr("sort - 1")).Error; err != nil {
logger.SugaredLogger.Error("向后排序更新失败: ", err.Error())
}
}
// 更新目标记录的排序
if err := db.Dao.Model(&FollowedStock{}).
Where("stock_code = ?", strings.ToLower(stockCode)).
Update("sort", newSort).Error; err != nil {
logger.SugaredLogger.Error("更新股票排序失败: ", err.Error())
}
}
} }
func (receiver StockDataApi) SetStockAICron(cron string, stockCode string) { func (receiver StockDataApi) SetStockAICron(cron string, stockCode string) {
if strutil.HasPrefixAny(stockCode, []string{"gb_"}) { if strutil.HasPrefixAny(stockCode, []string{"gb_"}) {
@ -619,146 +508,6 @@ func GB18030ToUTF8(bs []byte) string {
return string(d) return string(d)
} }
func ParseTxStockData(data string) (*StockInfo, error) {
//v_r_hk09660="100~地平线机器人-W~09660~6.240~5.690~5.800~192659034.0~0~0~6.240~0~0~0~0~0~0~0~0~0~6.240~0~0~0~0~0~0~0~0~0~192659034.0~2025/04/29
//13:41:04~0.550~9.67~6.450~5.710~6.240~192659034.0~1180471843.140~0~32.51~~0~0~13.01~691.1364~823.6983~HORIZONROBOT-W~0.00~10.380~3.320~1.07~-16.03~0~0~0~0~0~32.51~6.40~1.74~600~73.33~17.96~GP~19.70~11.51~-0.95~-18.54~44.44~13200293682.00~11075904412.00~32.51~0.000~6.127~56.39~HKD~1~30";
//v_sz002241="51~歌尔股份~002241~22.26~22.27~0.00~0~0~0~22.26~1004~0.00~0~0.00~0~0.00~0~0.00~0~22.26~1004~0.00~558~0.00~0~0.00~0~0.00~0~~20250509092233~-0.01~-0.04~0.00~0.00~22.26/0/0~0~0~0.00~28.21~~0.00~0.00~0.00~686.46~777.09~2.31~24.50~20.04~0.00~-558~0.00~41.44~29.16~~~1.24~0.0000~0.0000~0~
//~GP-A~-13.75~6.76~1.09~8.18~3.39~30.63~15.70~6.87~17.47~-23.95~3083811231~3490989083~-21.75~12.02~3083811231~~~39.36~-0.04~~CNY~0~~0.00~0";
datas := strutil.SplitAndTrim(data, "=", "\"")
if len(datas) < 2 {
return nil, fmt.Errorf("invalid data format")
}
var result map[string]string
var err error
if strutil.ContainsAny(datas[0], []string{"v_r_hk", "v_hk", "v_sz", "v_sh"}) {
result, err = ParseTxHKStockData(datas)
}
//logger.SugaredLogger.Infof("股票数据解析完成: %v", result)
marshal, err := json.Marshal(result)
if err != nil {
logger.SugaredLogger.Errorf("json.Marshal error:%s", err.Error())
return nil, err
}
//logger.SugaredLogger.Infof("股票数据解析完成marshal: %s", marshal)
stockInfo := &StockInfo{}
err = json.Unmarshal(marshal, &stockInfo)
if err != nil {
logger.SugaredLogger.Errorf("json.Unmarshal error:%s", err.Error())
return nil, err
}
//logger.SugaredLogger.Infof("股票数据解析完成stockInfo: %+v", stockInfo)
return stockInfo, nil
}
func ParseTxHKStockData(datas []string) (map[string]string, error) {
//v_r_hk09660="
//100~ 0
//地平线机器人-W~ 1
//09660~ 2
//6.270~ 3 当前价
//5.690~ 4 昨收价
//5.800~ 5 开盘价
//195083034.0~
//0~
//0~
//6.270~
//0~
//0~
//0~
//0~
//0~
//0~
//0~
//0~
//0~
//6.270~
//0~0~0~0~0~0~0~0~0~
//195083034.0~
//2025/04/29 13:45:41~ 30 当前时间
//0.580~
//10.19~
//6.450~ 最高价
//5.710~ 最低价
//6.270~
//195083034.0~
//1195673623.140~
//0~
//32.66
//~~0~0~13.01~694.4592~827.6584~HORIZONROBOT-W~0.00~10.380~3.320~1.06~-18.71~0~0~0~0~0~32.66~6.43~1.76~600~74.17~18.53~GP~19.70~11.51~-0.48~-18.15~45.14~13200293682.00~11075904412.00~32.66~0.000~6.129~57.14~HKD~1~30";
result := make(map[string]string)
stockCode := strutil.ReplaceWithMap(datas[0], map[string]string{
"v_r_": "",
"v_": "",
})
result["股票代码"] = stockCode
parts := strutil.SplitAndTrim(datas[1], "~")
//logger.SugaredLogger.Infof("股票数据解析完成 len: %v", len(parts))
if len(parts) < 35 {
return nil, fmt.Errorf("invalid data format")
}
result["股票名称"] = parts[1]
result["当前价格"] = parts[3]
result["昨日收盘价"] = parts[4]
result["今日开盘价"] = parts[5]
result["今日最高价"] = parts[33]
result["今日最低价"] = parts[34]
if strutil.HasPrefixAny(stockCode, []string{"sz", "sh"}) {
result["买一报价"] = parts[9]
result["买一申报"] = parts[10]
result["买二报价"] = parts[11]
result["买二申报"] = parts[12]
result["买三报价"] = parts[13]
result["买三申报"] = parts[14]
result["买四报价"] = parts[15]
result["买四申报"] = parts[16]
result["买五报价"] = parts[17]
result["买五申报"] = parts[18]
result["卖一报价"] = parts[19]
result["卖一申报"] = parts[20]
result["卖二报价"] = parts[21]
result["卖二申报"] = parts[22]
result["卖三报价"] = parts[23]
result["卖三申报"] = parts[24]
result["卖四报价"] = parts[25]
result["卖四申报"] = parts[26]
result["卖五报价"] = parts[27]
result["卖五申报"] = parts[28]
}
timestr := ""
if strutil.ContainsAny(parts[30], []string{"/"}) {
timestr = strutil.ReplaceWithMap(parts[30], map[string]string{
"/": "-",
"\n": " ",
})
result["日期"] = strutil.SplitAndTrim(timestr, " ", "")[0]
result["时间"] = strutil.SplitAndTrim(timestr, " ", "")[1]
} else {
result["日期"] = strutil.Trim(parts[29])[0:4] + "-" + strutil.Trim(parts[29])[4:6] + "-" + strutil.Trim(parts[29])[6:8]
result["时间"] = strutil.Trim(parts[29])[8:10] + ":" + strutil.Trim(parts[29])[10:12] + ":" + strutil.Trim(parts[29])[12:14]
result["今日最高价"] = parts[32]
result["今日最低价"] = parts[33]
}
//logger.SugaredLogger.Infof("股票数据解析完成 %s %s 时间: %s,%s", parts[1], parts[3], parts[29], parts[30])
//logger.SugaredLogger.Infof("股票数据解析完成 时间: %v", timestr)
//logger.SugaredLogger.Infof("股票数据解析完成: %v", result)
return result, nil
}
func ParseFullSingleStockData(data string) (*StockInfo, error) { func ParseFullSingleStockData(data string) (*StockInfo, error) {
datas := strutil.SplitAndTrim(data, "=", "\"") datas := strutil.SplitAndTrim(data, "=", "\"")
if len(datas) < 2 { if len(datas) < 2 {
@ -1342,66 +1091,6 @@ func CheckBrowserOnWindows() (string, bool) {
return path + "\\msedge.exe", true return path + "\\msedge.exe", true
} }
// 分时数据
func (receiver StockDataApi) GetStockMinutePriceData(stockCode string) (*[]MinuteData, string) {
url := fmt.Sprintf("https://web.ifzq.gtimg.cn/appstock/app/minute/query?code=%s", stockCode)
if strutil.HasPrefixAny(stockCode, []string{"gb_", "GB_"}) {
stockCode = strings.Replace(strings.ToUpper(stockCode), "GB_", "us", 1) + ".OQ"
}
if strutil.HasPrefixAny(stockCode, []string{"us", "US"}) {
url = fmt.Sprintf("https://web.ifzq.gtimg.cn/appstock/app/UsMinute/query?code=%s", stockCode)
}
logger.SugaredLogger.Infof("GetStockMinutePriceData url:%s", url)
res := make(map[string]interface{})
resp, err := receiver.client.SetTimeout(time.Duration(receiver.config.CrawlTimeOut)*time.Second).R().
SetHeader("Host", "web.ifzq.gtimg.cn").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
Get(url)
date := ""
minuteDatas := &[]MinuteData{}
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())
return minuteDatas, date
}
//logger.SugaredLogger.Infof("resp:%s", resp.Body())
json.Unmarshal(resp.Body(), &res)
code, _ := convertor.ToInt(res["code"])
if res["data"] != nil && code == 0 {
data := res["data"].(map[string]interface{})
if stockData, ok := data[stockCode]; ok {
m := stockData.(map[string]interface{})
if d, ok := m["data"]; ok {
if m2, ok := d.(map[string]any); ok {
minutePriceData := m2["data"]
datas := minutePriceData.([]any)
for _, item := range datas {
minuteDataSplit := strutil.SplitEx(strutil.ReplaceWithMap(item.(string), map[string]string{
"\r\n": " ",
}), " ", true)
price, _ := convertor.ToFloat(minuteDataSplit[1])
volume, _ := convertor.ToFloat(minuteDataSplit[2])
amount := float64(0)
if len(minuteDataSplit) >= 4 {
amount, _ = convertor.ToFloat(minuteDataSplit[3])
}
minuteData := &MinuteData{
Time: minuteDataSplit[0][0:2] + ":" + minuteDataSplit[0][2:4],
Price: price,
Volume: volume,
Amount: amount,
}
*minuteDatas = append(*minuteDatas, *minuteData)
}
date = m2["date"].(string)
}
}
}
}
return minuteDatas, date
}
func (receiver StockDataApi) GetKLineData(stockCode string, kLineType string, days int64) *[]KLineData { func (receiver StockDataApi) GetKLineData(stockCode string, kLineType string, days int64) *[]KLineData {
url := fmt.Sprintf("http://quotes.sina.cn/cn/api/json_v2.php/CN_MarketDataService.getKLineData?symbol=%s&scale=%s&ma=yes&datalen=%d", stockCode, kLineType, days) url := fmt.Sprintf("http://quotes.sina.cn/cn/api/json_v2.php/CN_MarketDataService.getKLineData?symbol=%s&scale=%s&ma=yes&datalen=%d", stockCode, kLineType, days)
K := &[]KLineData{} K := &[]KLineData{}
@ -1469,192 +1158,6 @@ func (receiver StockDataApi) GetHK_KLineData(stockCode string, kLineType string,
} }
return K return K
} }
func (receiver StockDataApi) GetSinaHKStockInfo() {
pageSize := 500
for i := 1; i <= 3060/pageSize; i++ {
infos := getSinaStockInfo(receiver, i, pageSize)
for i, info := range *infos {
logger.SugaredLogger.Infof("infos:%d,%s:%s", i, info.Symbol, info.Name)
}
}
}
func getSinaStockInfo(receiver StockDataApi, page, pageSize int) *[]models.SinaStockInfo {
infos := &[]models.SinaStockInfo{}
url := "https://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHKStockData?page=%d&num=%d&sort=symbol&asc=1&node=qbgg_hk&_s_r_a=init"
_, err := receiver.client.SetTimeout(time.Duration(receiver.config.CrawlTimeOut)*time.Second).SetProxy("http://localhost:10809").R().
SetHeader("Host", "vip.stock.finance.sina.com.cn").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
SetResult(infos).
Get(fmt.Sprintf(url, page, pageSize))
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())
}
return infos
}
func (receiver StockDataApi) getDCStockInfo(market string, page, pageSize int) {
//m:105,m:106,m:107 //美股
//m:128+t:3,m:128+t:4,m:128+t:1,m:128+t:2 //港股
fs := ""
switch market {
case "hk":
fs = "m:128+t:3,m:128+t:4,m:128+t:1,m:128+t:2"
case "us":
fs = "m:105,m:106,m:107"
}
url := "https://push2.eastmoney.com/api/qt/clist/get?cb=jQuery371047843066631541353_1745889398012&fs=%s&fields=f12,f13,f14,f19,f1,f2,f4,f3,f152,f17,f18,f15,f16,f5,f6&fid=f3&pn=%d&pz=%d&po=1&dect=1"
resp, err := receiver.client.SetTimeout(time.Duration(receiver.config.CrawlTimeOut)*time.Second).R().
SetHeader("Host", "push2.eastmoney.com").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
Get(fmt.Sprintf(url, fs, page, pageSize))
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())
return
}
body := string(resp.Body())
body = strutil.ReplaceWithMap(body, map[string]string{
"jQuery371047843066631541353_1745889398012(": "",
");": "",
})
js := "var d=" + body
vm := otto.New()
_, err = vm.Run(js)
_, err = vm.Run("var data = JSON.stringify(d);")
value, err := vm.Get("data")
data := make(map[string]any)
err = json.Unmarshal([]byte(value.String()), &data)
if err != nil {
logger.SugaredLogger.Errorf("json:%s", value.String())
logger.SugaredLogger.Errorf("json.Unmarshal error:%v", err.Error())
}
logger.SugaredLogger.Infof("resp:%s", data)
if data["data"] != nil {
datas := data["data"].(map[string]any)
total := datas["total"].(float64)
diff := datas["diff"].(map[string]any)
logger.SugaredLogger.Infof("total:%d", int(total))
for k, item := range diff {
stock := item.(map[string]any)
logger.SugaredLogger.Infof("k:%s,%s:%s", k, stock["f14"], stock["f12"])
if market == "hk" {
stockInfo := &models.StockInfoHK{
Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".HK",
Name: stock["f14"].(string),
}
db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", stockInfo.Code).First(stockInfo)
logger.SugaredLogger.Infof("stockInfo:%+v", stockInfo)
if stockInfo.ID == 0 {
db.Dao.Model(&models.StockInfoHK{}).Create(stockInfo)
}
}
if market == "us" {
stockInfo := &models.StockInfoUS{
Code: strutil.PadStart(stock["f12"].(string), 5, "0") + ".US",
Name: stock["f14"].(string),
}
db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", stockInfo.Code).First(stockInfo)
logger.SugaredLogger.Infof("stockInfo:%+v", stockInfo)
if stockInfo.ID == 0 {
db.Dao.Model(&models.StockInfoUS{}).Create(stockInfo)
}
}
}
}
}
func (receiver StockDataApi) GetHKStockInfo(pageSize int) {
url := "https://stock.gtimg.cn/data/hk_rank.php?board=main_all&metric=price&pageSize=%d&reqPage=1&order=desc&var_name=list_data"
resp, err := receiver.client.SetTimeout(time.Duration(receiver.config.CrawlTimeOut)*time.Second).R().
SetHeader("Host", "stock.gtimg.cn").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
Get(fmt.Sprintf(url, pageSize))
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())
return
}
js := "var " + string(resp.Body())
vm := otto.New()
_, err = vm.Run(js)
_, err = vm.Run("var data = JSON.stringify(list_data);")
if err != nil {
return
}
value, err := vm.Get("data")
data := make(map[string]any)
err = json.Unmarshal([]byte(value.String()), &data)
if err != nil {
logger.SugaredLogger.Errorf("json.Unmarshal error:%v", err.Error())
}
logger.SugaredLogger.Infof("resp:%s", data)
if data["code"] != nil && data["code"].(float64) == 0 {
d := data["data"].(map[string]any)
saveHKStockInfo(d)
page_count := int64(d["page_count"].(float64))
logger.SugaredLogger.Infof("page_count:%d", page_count)
page := int64(1)
for page > page_count {
urlx := fmt.Sprintf("https://stock.gtimg.cn/data/hk_rank.php?board=main_all&metric=price&pageSize=%d&reqPage=%d&order=desc&var_name=list_data", pageSize, page)
logger.SugaredLogger.Infof("url:%s", urlx)
resp, err = receiver.client.SetTimeout(time.Duration(receiver.config.CrawlTimeOut)*time.Second).R().
SetHeader("Host", "stock.gtimg.cn").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
Get(urlx)
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())
break
}
js = "var " + string(resp.Body())
_, err = vm.Run(js)
_, err = vm.Run("var data = JSON.stringify(list_data);")
if err != nil {
return
}
value, err = vm.Get("data")
data = make(map[string]any)
err = json.Unmarshal([]byte(value.String()), &data)
if err != nil {
logger.SugaredLogger.Errorf("json.Unmarshal error:%v", err.Error())
}
logger.SugaredLogger.Infof("resp:%s", data)
if data != nil && data["code"] != nil && data["code"].(float64) == 0 {
if data["data"] != nil {
d = data["data"].(map[string]any)
saveHKStockInfo(d)
}
}
page++
}
//
}
}
func saveHKStockInfo(d map[string]any) {
for _, v := range d["page_data"].([]any) {
vv := v.(string)
splits := strings.Split(vv, "~")
stock := &models.StockInfoHK{
Code: strutil.PadStart(splits[0], 5, "0") + ".HK",
Name: splits[1],
}
logger.SugaredLogger.Infof("vv:%s", vv)
db.Dao.Model(stock).Where("code = ?", stock.Code).First(stock)
if stock.ID == 0 {
logger.SugaredLogger.Infof("stock:%+v", stock)
db.Dao.Model(&models.StockInfoHK{}).Create(stock)
}
}
}
func (receiver StockDataApi) GetCommonKLineData(stockCode string, kLineType string, days int64) *[]KLineData { func (receiver StockDataApi) GetCommonKLineData(stockCode string, kLineType string, days int64) *[]KLineData {
@ -1759,10 +1262,3 @@ type KLineData struct {
Close string `json:"close"` Close string `json:"close"`
Volume string `json:"volume"` Volume string `json:"volume"`
} }
type MinuteData struct {
Time string `json:"time"`
Price float64 `json:"price"`
Volume float64 `json:"volume"`
Amount float64 `json:"amount"`
}

View File

@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/duke-git/lancet/v2/convertor" "github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/random"
"github.com/duke-git/lancet/v2/strutil" "github.com/duke-git/lancet/v2/strutil"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"go-stock/backend/db" "go-stock/backend/db"
@ -47,7 +46,6 @@ func TestGetFinancialReports(t *testing.T) {
} }
func TestGetTelegraphSearch(t *testing.T) { func TestGetTelegraphSearch(t *testing.T) {
db.Init("../../data/stock.db")
//url := "https://www.cls.cn/searchPage?keyword=%E9%97%BB%E6%B3%B0%E7%A7%91%E6%8A%80&type=telegram" //url := "https://www.cls.cn/searchPage?keyword=%E9%97%BB%E6%B3%B0%E7%A7%91%E6%8A%80&type=telegram"
messages := SearchStockInfo("谷歌", "telegram", 30) messages := SearchStockInfo("谷歌", "telegram", 30)
for _, message := range *messages { for _, message := range *messages {
@ -57,27 +55,21 @@ func TestGetTelegraphSearch(t *testing.T) {
//https://www.cls.cn/stock?code=sh600745 //https://www.cls.cn/stock?code=sh600745
} }
func TestSearchStockInfoByCode(t *testing.T) { func TestSearchStockInfoByCode(t *testing.T) {
db.Init("../../data/stock.db")
SearchStockInfoByCode("sh600745") SearchStockInfoByCode("sh600745")
} }
func TestSearchStockPriceInfo(t *testing.T) { func TestSearchStockPriceInfo(t *testing.T) {
db.Init("../../data/stock.db") db.Init("../../data/stock.db")
//SearchStockPriceInfo("中信证券", "hk06030", 30) //SearchStockPriceInfo("中信证券", "hk06030", 30)
SearchStockPriceInfo("上海贝岭", "sh600171", 30) //SearchStockPriceInfo("上海贝岭", "sh600171", 30)
//SearchStockPriceInfo("苹果公司", "gb_aapl", 30) SearchStockPriceInfo("苹果公司", "gb_aapl", 30)
//SearchStockPriceInfo("微创光电", "bj430198", 30) //SearchStockPriceInfo("微创光电", "bj430198", 30)
//getZSInfo("创业板指数", "sz399006", 30) getZSInfo("创业板指数", "sz399006", 30)
//getZSInfo("上证综合指数", "sh000001", 30) //getZSInfo("上证综合指数", "sh000001", 30)
//getZSInfo("沪深300指数", "sh000300", 30) //getZSInfo("沪深300指数", "sh000300", 30)
} }
func TestGetStockMinutePriceData(t *testing.T) {
db.Init("../../data/stock.db")
data, date := NewStockDataApi().GetStockMinutePriceData("usTSLA.OQ")
logger.SugaredLogger.Infof("date:%s", date)
logger.SugaredLogger.Infof("%+#v", *data)
}
func TestGetKLineData(t *testing.T) { func TestGetKLineData(t *testing.T) {
db.Init("../../data/stock.db") db.Init("../../data/stock.db")
k := NewStockDataApi().GetKLineData("sh600171", "240", 30) k := NewStockDataApi().GetKLineData("sh600171", "240", 30)
@ -104,27 +96,6 @@ func TestGetHK_KLineData(t *testing.T) {
} }
func TestGetHKStockInfo(t *testing.T) {
db.Init("../../data/stock.db")
//NewStockDataApi().GetHKStockInfo(200)
//NewStockDataApi().GetSinaHKStockInfo()
//m:105,m:106,m:107 //美股
//m:128+t:3,m:128+t:4,m:128+t:1,m:128+t:2 //港股
for i := 1; i <= 592; i++ {
NewStockDataApi().getDCStockInfo("us", i, 20)
time.Sleep(time.Duration(random.RandInt(1, 3)) * time.Second)
}
}
func TestParseTxStockData(t *testing.T) {
str := "v_r_hk09660=\"100~地平线机器人-W~09660~6.340~5.690~5.800~210980204.0~0~0~6.340~0~0~0~0~0~0~0~0~0~6.340~0~0~0~0~0~0~0~0~0~210980204.0~2025/04/29\n14:14:52~0.650~11.42~6.450~5.710~6.340~210980204.0~1295585259.040~0~33.03~~0~0~13.01~702.2123~836.8986~HORIZONROBOT-W~0.00~10.380~3.320~1.00~-53.74~0~0~0~0~0~33.03~6.50~1.90~600~76.11~19.85~GP~19.70~11.51~0.63~-17.23~46.76~13200293682.00~11075904412.00~33.03~0.000~6.141~58.90~HKD~1~30\";"
//str = "v_sz002241=\"51~歌尔股份~002241~22.26~22.27~0.00~0~0~0~22.26~1004~0.00~0~0.00~0~0.00~0~0.00~0~22.26~1004~0.00~558~0.00~0~0.00~0~0.00~0~~20250509092233~-0.01~-0.04~0.00~0.00~22.26/0/0~0~0~0.00~28.21~~0.00~0.00~0.00~686.46~777.09~2.31~24.50~20.04~0.00~-558~0.00~41.44~29.16~~~1.24~0.0000~0.0000~0~\n~GP-A~-13.75~6.76~1.09~8.18~3.39~30.63~15.70~6.87~17.47~-23.95~3083811231~3490989083~-21.75~12.02~3083811231~~~39.36~-0.04~~CNY~0~~0.00~0\";"
str = "v_sz002241=\"51~歌尔股份~002241~21.92~22.27~22.14~109872~40211~69642~21.91~25~21.90~961~21.89~257~21.88~748~21.87~665~21.92~86~21.93~168~21.94~556~21.95~171~21.96~85~~20250509094209~-0.35~-1.57~22.16~21.84~21.92/109872/241183171~109872~24118~0.36~27.78~~22.16~21.84~1.44~675.97~765.22~2.27~24.50~20.04~2.57~1590~21.95~40.80~28.71~~~1.24~24118.3171~0.0000~0~\n~GP-A~-15.07~5.13~1.11~8.18~3.39~30.63~15.70~5.23~15.67~-25.11~3083811231~3490989083~42.72~10.31~3083811231~~~37.23~0.18~~CNY~0~~21.85~1952\";"
//str = "v_r_hk09660=\"100~地平线机器人-W~09660~6.860~7.000~7.010~21157200.0~0~0~6.860~0~0~0~0~0~0~0~0~0~6.860~0~0~0~0~0~0~0~0~0~21157200.0~2025/05/09\n09:43:13~-0.140~-2.00~7.030~6.730~6.860~21157200.0~144331073.000~0~35.74~~0~0~4.29~759.8070~905.5401~HORIZONROBOT-W~0.00~10.380~3.320~2.93~11.10~0~0~0~0~0~35.74~7.04~0.19~600~90.56~4.73~GP~19.70~11.51~17.26~48.48~13.58~13200293682.00~11075904412.00~35.74~0.000~6.822~71.93~HKD~1~30\";"
info, _ := ParseTxStockData(str)
logger.SugaredLogger.Infof("%+#v", info)
}
func TestGetRealTimeStockPriceInfo(t *testing.T) { func TestGetRealTimeStockPriceInfo(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
@ -183,7 +154,7 @@ func TestParseFullSingleStockData(t *testing.T) {
func TestNewStockDataApi(t *testing.T) { func TestNewStockDataApi(t *testing.T) {
db.Init("../../data/stock.db") db.Init("../../data/stock.db")
stockDataApi := NewStockDataApi() stockDataApi := NewStockDataApi()
datas, _ := stockDataApi.GetStockCodeRealTimeData("sh600859", "sh600745", "gb_tsla", "hk09660", "hk00700") datas, _ := stockDataApi.GetStockCodeRealTimeData("sh600859", "sh600745", "gb_tsla")
for _, data := range *datas { for _, data := range *datas {
t.Log(data) t.Log(data)
} }

View File

@ -1,290 +0,0 @@
package data
import (
"bufio"
"fmt"
"github.com/go-ego/gse"
"go-stock/backend/logger"
"os"
"strings"
)
// 金融情感词典,包含股票市场相关的专业词汇
var (
seg gse.Segmenter
// 正面金融词汇及其权重
positiveFinanceWords = map[string]float64{
"上涨": 2.0, "涨停": 3.0, "牛市": 3.0, "反弹": 2.0, "新高": 2.5,
"利好": 2.5, "增持": 2.0, "买入": 2.0, "推荐": 1.5, "看多": 2.0,
"盈利": 2.0, "增长": 2.0, "超预期": 2.5, "强劲": 1.5, "回升": 1.5,
"复苏": 2.0, "突破": 2.0, "创新高": 3.0, "回暖": 1.5, "上扬": 1.5,
"利好消息": 3.0, "收益增长": 2.5, "利润增长": 2.5, "业绩优异": 2.5,
"潜力股": 2.0, "绩优股": 2.0, "强势": 1.5, "走高": 1.5, "攀升": 1.5,
"大涨": 2.5, "飙升": 3.0, "井喷": 3.0, "爆发": 2.5, "暴涨": 3.0,
}
// 负面金融词汇及其权重
negativeFinanceWords = map[string]float64{
"下跌": 2.0, "跌停": 3.0, "熊市": 3.0, "回调": 1.5, "新低": 2.5,
"利空": 2.5, "减持": 2.0, "卖出": 2.0, "看空": 2.0, "亏损": 2.5,
"下滑": 2.0, "萎缩": 2.0, "不及预期": 2.5, "疲软": 1.5, "恶化": 2.0,
"衰退": 2.0, "跌破": 2.0, "创新低": 3.0, "走弱": 1.5, "下挫": 1.5,
"利空消息": 3.0, "收益下降": 2.5, "利润下滑": 2.5, "业绩不佳": 2.5,
"垃圾股": 2.0, "风险股": 2.0, "弱势": 1.5, "走低": 1.5, "缩量": 2.5,
"大跌": 2.5, "暴跌": 3.0, "崩盘": 3.0, "跳水": 3.0, "重挫": 3.0,
}
// 否定词,用于反转情感极性
negationWords = map[string]struct{}{
"不": {}, "没": {}, "无": {}, "非": {}, "未": {}, "别": {}, "勿": {},
}
// 程度副词,用于调整情感强度
degreeWords = map[string]float64{
"非常": 1.8, "极其": 2.2, "太": 1.8, "很": 1.5,
"比较": 0.8, "稍微": 0.6, "有点": 0.7, "显著": 1.5,
"大幅": 1.8, "急剧": 2.0, "轻微": 0.6, "小幅": 0.7,
}
// 转折词,用于识别情感转折
transitionWords = map[string]struct{}{
"但是": {}, "然而": {}, "不过": {}, "却": {}, "可是": {},
}
)
func init() {
// 加载默认词典
err := seg.LoadDict()
if err != nil {
logger.SugaredLogger.Error(err.Error())
}
}
// SentimentResult 情感分析结果类型
type SentimentResult struct {
Score float64 // 情感得分
Category SentimentType // 情感类别
PositiveCount int // 正面词数量
NegativeCount int // 负面词数量
Description string // 情感描述
}
// SentimentType 情感类型枚举
type SentimentType int
const (
Positive SentimentType = iota
Negative
Neutral
)
// AnalyzeSentiment 判断文本的情感
func AnalyzeSentiment(text string) SentimentResult {
// 初始化得分
score := 0.0
positiveCount := 0
negativeCount := 0
// 分词(简单按单个字符分割)
words := splitWords(text)
// 检查文本是否包含转折词,并分割成两部分
var transitionIndex int
var hasTransition bool
for i, word := range words {
if _, ok := transitionWords[word]; ok {
transitionIndex = i
hasTransition = true
break
}
}
// 处理有转折的文本
if hasTransition {
// 转折前的部分
preTransitionWords := words[:transitionIndex]
preScore, prePos, preNeg := calculateScore(preTransitionWords)
// 转折后的部分,权重加倍
postTransitionWords := words[transitionIndex+1:]
postScore, postPos, postNeg := calculateScore(postTransitionWords)
postScore *= 1.5 // 转折后的情感更重要
score = preScore + postScore
positiveCount = prePos + postPos
negativeCount = preNeg + postNeg
} else {
// 没有转折的文本
score, positiveCount, negativeCount = calculateScore(words)
}
// 确定情感类别
var category SentimentType
switch {
case score > 1.0:
category = Positive
case score < -1.0:
category = Negative
default:
category = Neutral
}
return SentimentResult{
Score: score,
Category: category,
PositiveCount: positiveCount,
NegativeCount: negativeCount,
Description: GetSentimentDescription(category),
}
}
// 计算情感得分
func calculateScore(words []string) (float64, int, int) {
score := 0.0
positiveCount := 0
negativeCount := 0
// 遍历每个词,计算情感得分
for i, word := range words {
// 首先检查是否为程度副词
degree, isDegree := degreeWords[word]
// 检查是否为否定词
_, isNegation := negationWords[word]
// 检查是否为金融正面词
if posScore, isPositive := positiveFinanceWords[word]; isPositive {
// 检查前一个词是否为否定词或程度副词
if i > 0 {
prevWord := words[i-1]
if _, isNeg := negationWords[prevWord]; isNeg {
score -= posScore
negativeCount++
continue
}
if deg, isDeg := degreeWords[prevWord]; isDeg {
score += posScore * deg
positiveCount++
continue
}
}
score += posScore
positiveCount++
continue
}
// 检查是否为金融负面词
if negScore, isNegative := negativeFinanceWords[word]; isNegative {
// 检查前一个词是否为否定词或程度副词
if i > 0 {
prevWord := words[i-1]
if _, isNeg := negationWords[prevWord]; isNeg {
score += negScore
positiveCount++
continue
}
if deg, isDeg := degreeWords[prevWord]; isDeg {
score -= negScore * deg
negativeCount++
continue
}
}
score -= negScore
negativeCount++
continue
}
// 处理程度副词(如果后面跟着情感词)
if isDegree && i+1 < len(words) {
nextWord := words[i+1]
if posScore, isPositive := positiveFinanceWords[nextWord]; isPositive {
score += posScore * degree
positiveCount++
continue
}
if negScore, isNegative := negativeFinanceWords[nextWord]; isNegative {
score -= negScore * degree
negativeCount++
continue
}
}
// 处理否定词(如果后面跟着情感词)
if isNegation && i+1 < len(words) {
nextWord := words[i+1]
if posScore, isPositive := positiveFinanceWords[nextWord]; isPositive {
score -= posScore
negativeCount++
continue
}
if negScore, isNegative := negativeFinanceWords[nextWord]; isNegative {
score += negScore
positiveCount++
continue
}
}
}
return score, positiveCount, negativeCount
}
// 简单的分词函数,考虑了中文和英文
func splitWords(text string) []string {
return seg.Cut(text, true)
}
// GetSentimentDescription 获取情感类别的文本描述
func GetSentimentDescription(category SentimentType) string {
switch category {
case Positive:
return "看涨"
case Negative:
return "看跌"
case Neutral:
return "中性"
default:
return "未知"
}
}
func main() {
// 从命令行读取输入
reader := bufio.NewReader(os.Stdin)
fmt.Println("请输入要分析的股市相关文本输入exit退出")
for {
fmt.Print("> ")
text, err := reader.ReadString('\n')
if err != nil {
fmt.Println("读取输入时出错:", err)
continue
}
// 去除换行符
text = strings.TrimSpace(text)
// 检查是否退出
if text == "exit" {
break
}
// 分析情感
result := AnalyzeSentiment(text)
// 输出结果
fmt.Printf("情感分析结果: %s (得分: %.2f, 正面词:%d, 负面词:%d)\n",
GetSentimentDescription(result.Category),
result.Score,
result.PositiveCount,
result.NegativeCount)
}
}

View File

@ -1,36 +0,0 @@
package data
import (
"fmt"
"strings"
"testing"
)
// @Author spark
// @Date 2025/6/19 13:05
// @Desc
//-----------------------------------------------------------------------------------
func TestAnalyzeSentiment(t *testing.T) {
// 分析情感
text := " 【调查韩国近两成中小学生过度使用智能手机或互联网】财联社6月19日电韩国女性家族部18日公布的一项年度调查结果显示接受调查的韩国中小学生中共计约17.3%、即超过21万人使用智能手机或互联网的程度达到了“危险水平”这意味着他们因过度依赖智能手机或互联网而需要关注或干预这一比例引人担忧。 (新华社)\n"
text = "消息人士称联合利华Unilever正在为Graze零食品牌寻找买家。\n"
text = "【韩国未来5年将投入51万亿韩元发展文化产业】 据韩联社韩国文化体育观光部文体部今后5年将投入51万亿韩元约合人民币2667亿元预算落实总统李在明在竞选时期提出的“将韩国打造成全球五大文化强国之一”的承诺。\n"
//text = "【油气股持续拉升 国际实业午后涨停】财联社6月19日电油气股午后持续拉升国际实业、宝莫股份午后涨停准油股份、山东墨龙。茂化实华此前涨停通源石油、海默科技、贝肯能源、中曼石油、科力股份等多股涨超5%。\n"
//text = " 【三大指数均跌逾1% 下跌个股近4800只】财联社6月19日电指数持续走弱沪指下挫跌逾1.00%深成指跌1.25%创业板指跌1.39%。核聚变、风电、军工、食品消费等板块指数跌幅居前沪深京三市下跌个股近4800只。\n"
text = "【银行理财首单网下打新落地】财联社6月20日电记者从多渠道获悉光大理财以申报价格17元参与信通电子网下打新并成功入围有效报价成为行业内首家参与网下打新的银行理财公司。光大理财工作人员向证券时报记者表示本次光大理财是以其管理的混合类产品“阳光橙增盈绝对收益策略”参与了此次网下打新该产品为光大理财“固收+”银行理财产品。资料显示信通电子成立于1996年核心产品包括输电线路智能巡检系统、变电站智能辅控系统、移动智能终端及其他产品。根据其招股说明书信通电子2023、2024年营业收入分别较上年增长19.08%和7.97%净利润分别较上年增长5.6%和15.11%。 (证券时报)"
text = " 【以军称拦截数枚伊朗导弹】财联社6月20日电据央视新闻报道以军在贝尔谢巴及周边区域拦截了数枚伊朗导弹但仍有导弹或拦截残骸落地。以色列国防军发文表示搜救队伍正在一处“空中物体落地”的所在区域开展工作公众目前可以离开避难场所。伊朗方面对上述说法暂无回应。"
words := splitWords(text)
fmt.Println(strings.Join(words, " "))
result := AnalyzeSentiment(text)
// 输出结果
fmt.Printf("情感分析结果: %s (得分: %.2f, 正面词:%d, 负面词:%d)\n",
result.Description,
result.Score,
result.PositiveCount,
result.NegativeCount)
}

View File

@ -226,7 +226,6 @@ type Telegraph struct {
Url string `json:"url"` Url string `json:"url"`
Source string `json:"source"` Source string `json:"source"`
TelegraphTags []TelegraphTags `json:"tags" gorm:"-:migration;foreignKey:TelegraphId"` TelegraphTags []TelegraphTags `json:"tags" gorm:"-:migration;foreignKey:TelegraphId"`
SentimentResult string `json:"sentimentResult" gorm:"-:all"`
} }
type TelegraphTags struct { type TelegraphTags struct {
gorm.Model gorm.Model
@ -251,114 +250,3 @@ func (p Tags) TableName() string {
func (p Telegraph) TableName() string { func (p Telegraph) TableName() string {
return "telegraph_list" return "telegraph_list"
} }
type SinaStockInfo struct {
Symbol string `json:"symbol"`
Name string `json:"name"`
Engname string `json:"engname"`
Tradetype string `json:"tradetype"`
Lasttrade string `json:"lasttrade"`
Prevclose string `json:"prevclose"`
Open string `json:"open"`
High string `json:"high"`
Low string `json:"low"`
Volume string `json:"volume"`
Currentvolume string `json:"currentvolume"`
Amount string `json:"amount"`
Ticktime string `json:"ticktime"`
Buy string `json:"buy"`
Sell string `json:"sell"`
High52Week string `json:"high_52week"`
Low52Week string `json:"low_52week"`
Eps string `json:"eps"`
Dividend string `json:"dividend"`
StocksSum string `json:"stocks_sum"`
Pricechange string `json:"pricechange"`
Changepercent string `json:"changepercent"`
MarketValue string `json:"market_value"`
PeRatio string `json:"pe_ratio"`
}
type LongTigerRankData struct {
ACCUMAMOUNT float64 `json:"ACCUM_AMOUNT"`
BILLBOARDBUYAMT float64 `json:"BILLBOARD_BUY_AMT"`
BILLBOARDDEALAMT float64 `json:"BILLBOARD_DEAL_AMT"`
BILLBOARDNETAMT float64 `json:"BILLBOARD_NET_AMT"`
BILLBOARDSELLAMT float64 `json:"BILLBOARD_SELL_AMT"`
CHANGERATE float64 `json:"CHANGE_RATE"`
CLOSEPRICE float64 `json:"CLOSE_PRICE"`
DEALAMOUNTRATIO float64 `json:"DEAL_AMOUNT_RATIO"`
DEALNETRATIO float64 `json:"DEAL_NET_RATIO"`
EXPLAIN string `json:"EXPLAIN"`
EXPLANATION string `json:"EXPLANATION"`
FREEMARKETCAP float64 `json:"FREE_MARKET_CAP"`
SECUCODE string `json:"SECUCODE" gorm:"index"`
SECURITYCODE string `json:"SECURITY_CODE"`
SECURITYNAMEABBR string `json:"SECURITY_NAME_ABBR"`
SECURITYTYPECODE string `json:"SECURITY_TYPE_CODE"`
TRADEDATE string `json:"TRADE_DATE" gorm:"index"`
TURNOVERRATE float64 `json:"TURNOVERRATE"`
}
func (l LongTigerRankData) TableName() string {
return "long_tiger_rank"
}
type TVNews struct {
Id string `json:"id"`
Title string `json:"title"`
Published int `json:"published"`
Urgency int `json:"urgency"`
Permission string `json:"permission"`
StoryPath string `json:"storyPath"`
Provider struct {
Id string `json:"id"`
Name string `json:"name"`
LogoId string `json:"logo_id"`
} `json:"provider"`
}
type XUEQIUHot struct {
Data struct {
Items []HotItem `json:"items"`
ItemsSize int `json:"items_size"`
} `json:"data"`
ErrorCode int `json:"error_code"`
ErrorDescription string `json:"error_description"`
}
type HotItem struct {
Type int `json:"type"`
Code string `json:"code"`
Name string `json:"name"`
Value float64 `json:"value"`
Increment int `json:"increment"`
RankChange int `json:"rank_change"`
HasExist interface{} `json:"has_exist"`
Symbol string `json:"symbol"`
Percent float64 `json:"percent"`
Current float64 `json:"current"`
Chg float64 `json:"chg"`
Exchange string `json:"exchange"`
StockType int `json:"stock_type"`
SubType string `json:"sub_type"`
Ad int `json:"ad"`
AdId interface{} `json:"ad_id"`
ContentId interface{} `json:"content_id"`
Page interface{} `json:"page"`
Model interface{} `json:"model"`
Location interface{} `json:"location"`
TradeSession interface{} `json:"trade_session"`
CurrentExt interface{} `json:"current_ext"`
PercentExt interface{} `json:"percent_ext"`
}
type HotEvent struct {
PicSize interface{} `json:"pic_size"`
Tag string `json:"tag"`
Id int `json:"id"`
Pic string `json:"pic"`
Hot int `json:"hot"`
StatusCount int `json:"status_count"`
Content string `json:"content"`
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@vavt/cm-extension": "^1.8.0", "@vavt/cm-extension": "^1.8.0",
"@vavt/v3-extension": "^3.0.0", "@vavt/v3-extension": "^3.0.0",
"date-fns": "^4.1.0", "@vicons/ionicons5": "^0.13.0",
"echarts": "^5.6.0", "echarts": "^5.6.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
@ -23,19 +23,11 @@
"vue3-danmaku": "^1.6.1" "vue3-danmaku": "^1.6.1"
}, },
"devDependencies": { "devDependencies": {
"@vicons/antd": "^0.13.0",
"@vicons/carbon": "^0.13.0",
"@vicons/fa": "^0.13.0",
"@vicons/fluent": "^0.13.0",
"@vicons/ionicons4": "^0.13.0",
"@vicons/ionicons5": "^0.13.0",
"@vicons/material": "^0.13.0",
"@vicons/tabler": "^0.13.0",
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "^5.2.1",
"html-docx-js-typescript": "^0.1.5", "html-docx-js-typescript": "^0.1.5",
"naive-ui": "^2.41.0", "naive-ui": "^2.41.0",
"vfonts": "^0.0.3", "vfonts": "^0.0.3",
"vite": "^6.3.5" "vite": "^5.4.12"
}, },
"keywords": [ "keywords": [
"AI赋能股票分析", "AI赋能股票分析",

View File

@ -1 +1 @@
2d63c3a999d797889c01d6c96451b197 99aeae4d0e7cbe900b379d3e7d2f44d7

View File

@ -1,56 +1,33 @@
<script setup> <script setup>
import { import {
EventsEmit, EventsEmit,
EventsOff,
EventsOn, EventsOn,
Quit, Quit,
WindowFullscreen, WindowFullscreen, WindowGetPosition,
WindowHide, WindowHide,
WindowSetPosition,
WindowUnfullscreen WindowUnfullscreen
} from '../wailsjs/runtime' } from '../wailsjs/runtime'
import {h, onBeforeMount, onBeforeUnmount, onMounted, ref} from "vue"; import {h, onBeforeMount, onMounted, ref} from "vue";
import {RouterLink, useRouter} from 'vue-router' import { RouterLink } from 'vue-router'
import {createDiscreteApi,darkTheme,lightTheme , NIcon, NText,dateZhCN,zhCN} from 'naive-ui' import {darkTheme, NGradientText, NIcon, NText,} from 'naive-ui'
import { import {
AlarmOutline, SettingsOutline,
AnalyticsOutline,
BarChartSharp, Bonfire, BonfireOutline, EaselSharp,
ExpandOutline, Flag,
Flame, FlameSharp, InformationOutline,
LogoGithub,
NewspaperOutline,
NewspaperSharp, Notifications,
PowerOutline, Pulse,
ReorderTwoOutline, ReorderTwoOutline,
SettingsOutline, Skull, SkullOutline, SkullSharp, ExpandOutline,
SparklesOutline, PowerOutline, LogoGithub, MoveOutline, WalletOutline, StarOutline, AlarmOutline, SparklesOutline, NewspaperOutline,
StarOutline,
Wallet, WarningOutline,
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import {AnalyzeSentiment, GetConfig, GetGroupList} from "../wailsjs/go/main/App"; import {GetConfig} from "../wailsjs/go/main/App";
import {Dragon, Fire, Gripfire} from "@vicons/fa";
import {ReportSearch} from "@vicons/tabler";
import {LocalFireDepartmentRound} from "@vicons/material";
import {BoxSearch20Regular, CommentNote20Filled} from "@vicons/fluent";
import {FireFilled, FireOutlined, NotificationFilled, StockOutlined} from "@vicons/antd";
const router = useRouter()
const loading = ref(true)
const loadingMsg = ref("加载数据中...")
const enableNews= ref(false) const enableNews= ref(false)
const contentStyle = ref("") const contentStyle = ref("")
const enableFund = ref(false) const enableFund = ref(false)
const enableDarkTheme = ref(null) const enableDarkTheme = ref(null)
const content = ref('数据来源于网络,仅供参考;投资有风险,入市需谨慎\n\n未经授权,禁止商业目的!') const content = ref('数据来源于网络,仅供参考;投资有风险,入市需谨慎')
const isFullscreen = ref(false) const isFullscreen = ref(false)
const activeKey = ref('') const activeKey = ref('')
const containerRef= ref({}) const containerRef= ref({})
const realtimeProfit= ref(0) const realtimeProfit= ref(0)
const telegraph= ref([]) const telegraph= ref([])
const groupList = ref([])
const menuOptions = ref([ const menuOptions = ref([
{ {
label: () => label: () =>
@ -59,11 +36,8 @@ const menuOptions = ref([
{ {
to: { to: {
name: 'stock', name: 'stock',
query: { params: {
groupName: '全部',
groupId: 0,
}, },
params: {},
} }
}, },
{ default: () => '股票自选',} { default: () => '股票自选',}
@ -72,289 +46,29 @@ const menuOptions = ref([
icon: renderIcon(StarOutline), icon: renderIcon(StarOutline),
children:[ children:[
{ {
label: () => label: ()=> h(NText, {type:realtimeProfit.value>0?'error':'success'},{ default: () => '当日盈亏 '+realtimeProfit.value+"¥"}),
h( key: 'realtimeProfit',
'a', show: realtimeProfit.value,
{ icon: renderIcon(WalletOutline),
href: '#',
type: 'info',
onClick: () => {
//console.log("push",item)
router.push({
name: 'stock',
query: {
groupName: '全部',
groupId: 0,
},
})
EventsEmit("changeTab", {ID: 0, name: '全部'})
},
to: {
name: 'stock',
query: {
groupName: '全部',
groupId: 0,
},
}
},
{default: () => '全部',}
),
key: 0,
}
],
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
params: {}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '市场快讯'})
},
},
{default: () => '市场行情'}
),
key: 'market',
icon: renderIcon(NewspaperOutline),
children: [
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "市场快讯",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '市场快讯'})
},
},
{default: () => '市场快讯',}
),
key: 'market1',
icon: renderIcon(NewspaperSharp),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "全球股指",
},
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '全球股指'})
},
},
{default: () => '全球股指',}
),
key: 'market2',
icon: renderIcon(BarChartSharp),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "指标行情",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '指标行情'})
},
},
{default: () => '指标行情',}
),
key: 'market3',
icon: renderIcon(AnalyticsOutline),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "行业排名",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '行业排名'})
},
},
{default: () => '行业排名',}
),
key: 'market4',
icon: renderIcon(Flag),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "个股资金流向",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '个股资金流向'})
},
},
{default: () => '个股资金流向',}
),
key: 'market5',
icon: renderIcon(Pulse),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "龙虎榜",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '龙虎榜'})
},
},
{default: () => '龙虎榜',}
),
key: 'market6',
icon: renderIcon(Dragon),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "个股研报",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '个股研报'})
},
},
{default: () => '个股研报',}
),
key: 'market7',
icon: renderIcon(StockOutlined),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "公司公告",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '公司公告'})
},
},
{default: () => '公司公告',}
),
key: 'market8',
icon: renderIcon(NotificationFilled),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "行业研究",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '行业研究'})
},
},
{default: () => '行业研究',}
),
key: 'market9',
icon: renderIcon(ReportSearch),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "当前热门",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '当前热门'})
},
},
{default: () => '当前热门',}
),
key: 'market10',
icon: renderIcon(Gripfire),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "指标选股",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '指标选股'})
},
},
{default: () => '指标选股',}
),
key: 'market11',
icon: renderIcon(BoxSearch20Regular),
}, },
] ]
}, },
{
label: () =>
h(
RouterLink,
{
to: {
name: 'market',
params: {
}
}
},
{ default: () => '市场行情' }
),
key: 'market',
icon: renderIcon(NewspaperOutline),
},
{ {
label: () => label: () =>
h( h(
@ -362,7 +76,8 @@ const menuOptions = ref([
{ {
to: { to: {
name: 'fund', name: 'fund',
params: {}, params: {
},
} }
}, },
{ default: () => '基金自选',} { default: () => '基金自选',}
@ -386,7 +101,8 @@ const menuOptions = ref([
{ {
to: { to: {
name: 'settings', name: 'settings',
params: {} params: {
}
} }
}, },
{ default: () => '设置' } { default: () => '设置' }
@ -401,7 +117,8 @@ const menuOptions = ref([
{ {
to: { to: {
name: 'about', name: 'about',
params: {} params: {
}
} }
}, },
{ default: () => '关于' } { default: () => '关于' }
@ -445,11 +162,9 @@ const menuOptions = ref([
icon: renderIcon(PowerOutline), icon: renderIcon(PowerOutline),
}, },
]) ])
function renderIcon(icon) { function renderIcon(icon) {
return () => h(NIcon, null, { default: () => h(icon) }) return () => h(NIcon, null, { default: () => h(icon) })
} }
function toggleFullscreen(e) { function toggleFullscreen(e) {
//console.log(e) //console.log(e)
if (isFullscreen.value) { if (isFullscreen.value) {
@ -461,7 +176,6 @@ function toggleFullscreen(e) {
} }
isFullscreen.value=!isFullscreen.value isFullscreen.value=!isFullscreen.value
} }
// const drag = ref(false) // const drag = ref(false)
// const lastPos= ref({x:0,y:0}) // const lastPos= ref({x:0,y:0})
// function toggleStartMoveWindow(e) { // function toggleStartMoveWindow(e) {
@ -486,24 +200,6 @@ EventsOn("telegraph", (data) => {
telegraph.value=data telegraph.value=data
}) })
EventsOn("loadingMsg", (data) => {
if(data==="done"){
loadingMsg.value = "加载完成..."
EventsEmit("loadingDone", "app")
loading.value = false
}else{
loading.value = true
loadingMsg.value = data
}
})
onBeforeUnmount(() => {
EventsOff("realtime_profit")
EventsOff("loadingMsg")
EventsOff("telegraph")
EventsOff("newsPush")
})
window.onerror = function (msg, source, lineno, colno, error) { window.onerror = function (msg, source, lineno, colno, error) {
// //
EventsEmit("frontendError", { EventsEmit("frontendError", {
@ -518,52 +214,8 @@ window.onerror = function (msg, source, lineno, colno, error) {
}; };
onBeforeMount(()=>{ onBeforeMount(()=>{
GetGroupList().then(result => {
groupList.value = result
menuOptions.value.map((item) => {
//console.log(item)
if (item.key === 'stock') {
item.children.push(...groupList.value.map(item => {
return {
label: () =>
h(
'a',
{
href: '#',
type: 'info',
onClick: () => {
//console.log("push",item)
router.push({
name: 'stock',
query: {
groupName: item.name,
groupId: item.ID,
},
})
setTimeout(() => {
EventsEmit("changeTab", item)
}, 100)
},
to: {
name: 'stock',
query: {
groupName: item.name,
groupId: item.ID,
},
}
},
{default: () => item.name,}
),
key: item.ID,
}
}))
}
})
})
GetConfig().then((res)=>{ GetConfig().then((res)=>{
//console.log(res) console.log(res)
enableFund.value=res.enableFund enableFund.value=res.enableFund
menuOptions.value.filter((item)=>{ menuOptions.value.filter((item)=>{
@ -581,49 +233,23 @@ onBeforeMount(() => {
}) })
onMounted(()=>{ onMounted(()=>{
contentStyle.value = "max-height: calc(92vh);overflow: hidden" contentStyle.value="max-height: calc(90vh);overflow: hidden"
GetConfig().then((res)=>{ GetConfig().then((res)=>{
if(res.enableNews){ if(res.enableNews){
enableNews.value=true enableNews.value=true
} }
enableFund.value=res.enableFund enableFund.value=res.enableFund
const {notification } =createDiscreteApi(["notification"], {
configProviderProps: {
theme: enableDarkTheme.value ? darkTheme : lightTheme ,
max: 3,
},
})
EventsOn("newsPush", (data) => {
//console.log(data)
if(data.isRed){
notification.create({
//type:"error",
// avatar: () => h(NIcon,{component:Notifications,color:"red"}),
title: data.time,
content: () => h(NText,{type:"error"}, { default: () => data.content }),
meta: () => h(NText,{type:"warning"}, { default: () => data.source}),
duration:1000*40,
})
}else{
notification.create({
//type:"info",
//avatar: () => h(NIcon,{component:Notifications}),
title: data.time,
content: () => h(NText,{type:"info"}, { default: () => data.content }),
meta: () => h(NText,{type:"warning"}, { default: () => data.source}),
duration:1000*30 ,
})
}
})
}) })
}) })
</script> </script>
<template> <template>
<n-config-provider ref="containerRef" :theme="enableDarkTheme" :locale="zhCN" :date-locale="dateZhCN"> <n-config-provider ref="containerRef" :theme="enableDarkTheme" >
<n-message-provider > <n-message-provider >
<n-notification-provider> <n-notification-provider>
<n-modal-provider> <n-modal-provider>
<n-dialog-provider> <n-dialog-provider>
<n-watermark <n-watermark
:content="content" :content="content"
cross cross
@ -638,23 +264,25 @@ onMounted(() => {
> >
<n-flex> <n-flex>
<n-grid x-gap="12" :cols="1"> <n-grid x-gap="12" :cols="1">
<!--
<n-gi style="position: relative;top:1px;z-index: 19;width: 100%" v-if="telegraph.length>0">
</n-gi>
-->
<n-gi> <n-gi>
<n-spin :show="loading"> <n-marquee :speed="100" style="position: relative;top:0;z-index: 19;width: 100%" v-if="(telegraph.length>0)&&(enableNews)">
<template #description>
{{ loadingMsg }}
</template>
<n-marquee :speed="100" style="position: relative;top:0;z-index: 19;width: 100%"
v-if="(telegraph.length>0)&&(enableNews)">
<n-tag type="warning" v-for="item in telegraph" style="margin-right: 10px"> <n-tag type="warning" v-for="item in telegraph" style="margin-right: 10px">
{{item}} {{item}}
</n-tag> </n-tag>
<!-- <n-text type="warning"> {{telegraph[0]}}</n-text>-->
</n-marquee> </n-marquee>
<n-scrollbar :style="contentStyle"> <n-scrollbar :style="contentStyle">
<n-skeleton v-if="loading" height="calc(100vh)" />
<RouterView /> <RouterView />
</n-scrollbar> </n-scrollbar>
</n-spin>
</n-gi> </n-gi>
<n-gi style="position: fixed;bottom:0;z-index: 9;width: 100%;"> <n-gi style="position: fixed;bottom:0;z-index: 9;width: 100%;">
<n-card size="small" style="--wails-draggable:drag"> <n-card size="small" style="--wails-draggable:drag">
<n-menu style="font-size: 18px;" <n-menu style="font-size: 18px;"

View File

@ -1,102 +0,0 @@
<script setup lang="ts">
import {nextTick, onBeforeMount, onMounted, onUnmounted, ref} from 'vue'
import {ClsCalendar} from "../../wailsjs/go/main/App";
import { addMonths, format ,parse} from 'date-fns';
import { zhCN } from 'date-fns/locale';
import {useMessage} from 'naive-ui'
import {Star48Filled} from "@vicons/fluent";
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0'); // 0+1
const day = String(today.getDate()).padStart(2, '0');
// YYYY-MM-DD
const formattedDate = `${year}-${month}-${day}`;
const formattedYM = `${year}-${month}`;
const list = ref([])
const message=useMessage()
function goBackToday() {
setTimeout(() => {
nextTick(
() => {
const elementById = document.getElementById(formattedDate);
if (elementById) {
elementById.scrollIntoView({
behavior: 'auto',
block: 'start'
})
}
}
)
}, 500)
}
onBeforeMount(() => {
ClsCalendar().then(res => {
list.value = res
goBackToday();
})
})
function getweekday(date){
let day=parse(date, 'yyyy-MM-dd', new Date())
return format(day, 'EEEE', {locale: zhCN})
}
</script>
<template>
<!-- <n-timeline size="large" style="text-align: left">-->
<!-- <n-timeline-item v-for="item in list" :key="item.date" :title="item.date" type="info" >-->
<!-- <n-list>-->
<!-- <n-list-item v-for="l in item.list" :key="l.article_id ">-->
<!-- <n-text>{{l.title}}</n-text>-->
<!-- </n-list-item>-->
<!-- </n-list>-->
<!-- </n-timeline-item>-->
<!-- </n-timeline>-->
<n-list bordered style="max-height: calc(100vh - 230px);text-align: left;">
<n-scrollbar style="max-height: calc(100vh - 230px);" >
<n-list-item v-for="(item, index) in list" :id="item.calendar_day" :key="item.calendar_day">
<n-thing :title="item.calendar_day +' '+item.week">
<n-list :bordered="false" hoverable>
<n-list-item v-for="(l,i ) in item.items" :key="l.id ">
<n-flex justify="space-between">
<n-text :type="item.calendar_day===formattedDate?'warning':'info'">{{i+1}}# {{l.title}}
<n-tag v-if="l.event" size="small" round type="success">事件</n-tag>
<n-tag v-if="l.economic" size="small" round type="error">数据</n-tag>
</n-text>
<n-rate v-if="l.event&&(l.event.star>0)" readonly :default-value="l.event.star">
<n-icon :component="Star48Filled"/>
</n-rate>
<n-rate v-if="l.economic&&(l.economic.star>0)" readonly :default-value="l.economic.star" >
<n-icon :component="Star48Filled"/>
</n-rate>
</n-flex>
<n-flex v-if="l.economic">
<n-tag type="warning" :bordered="false" :size="'small'">公布{{l.economic.actual }}</n-tag>
<n-tag type="warning" :bordered="false" :size="'small'">预测{{l.economic.consensus}}</n-tag>
<n-tag type="warning" :bordered="false" :size="'small'">前值{{l.economic.front}}</n-tag>
</n-flex>
</n-list-item>
</n-list>
</n-thing>
</n-list-item>
<n-list-item v-if="list.length==0">
<n-text type="info">没有数据</n-text>
</n-list-item>
<n-list-item v-else style="text-align: center;">
<n-button-group>
<n-button strong secondary type="warning" @click="goBackToday">回到今天</n-button>
</n-button-group>
</n-list-item>
</n-scrollbar>
</n-list>
</template>
<style scoped>
</style>

View File

@ -1,37 +0,0 @@
<script setup lang="ts">
import {onBeforeMount, onUnmounted, ref} from 'vue'
import {HotEvent} from "../../wailsjs/go/main/App";
const list = ref([])
const task =ref()
onBeforeMount(async () => {
list.value = await HotEvent(50)
task.value=setInterval(async ()=>{
list.value = await HotEvent(50)
}, 1000*10)
})
onUnmounted(async ()=>{
clearInterval(task.value)
})
</script>
<template>
<n-list bordered>
<template #header>
雪球热门
</template>
<n-list-item v-for="(item, index) in list" :key="index">
<n-thing :title="item.tag" :description="item.content" >
<template v-if="item.pic" #avatar>
<n-avatar :src="item.pic" :size="60">
</n-avatar>
</template>
</n-thing>
</n-list-item>
</n-list>
</template>
<style scoped>
</style>

View File

@ -1,88 +0,0 @@
<script setup lang="ts">
import {onBeforeMount, onUnmounted, ref} from 'vue'
import {HotStock} from "../../wailsjs/go/main/App";
import KLineChart from "./KLineChart.vue";
import {ArrowBack, ArrowDown, ArrowUp} from "@vicons/ionicons5";
const {marketType}=defineProps(
{
marketType: {
type: String,
default: '10'
}
}
)
const task =ref()
const list = ref([])
onBeforeMount(async () => {
list.value = await HotStock(marketType)
task.value = setInterval(async () => {
list.value = await HotStock(marketType)
}, 5000)
})
onUnmounted(()=>{
clearInterval(task.value)
})
function getMarketCode(item) {
if (item.exchange === 'SZ') {
return item.code.toLowerCase()
}
if (item.exchange === 'SH') {
return item.code.toLowerCase()
}
if (item.exchange === 'HK') {
return (item.exchange + item.code).toLowerCase()
}
return ("gb_"+item.code).toLowerCase()
}
</script>
<template>
<n-table striped size="small">
<n-thead>
<n-tr>
<n-th>股票名称</n-th>
<n-th>涨跌幅</n-th>
<n-th>当前价格</n-th>
<n-th>热度</n-th>
<n-th>热度变化</n-th>
<n-th>排名变化</n-th>
</n-tr>
</n-thead>
<n-tbody>
<n-tr v-for="item in list" :key="item.code">
<n-td><n-text type="info">
<n-popover trigger="hover" placement="right">
<template #trigger>
<n-tag type="info" :bordered="false"> {{item.name}} {{item.code}}</n-tag>
</template>
<k-line-chart style="width: 800px" :code="getMarketCode(item)" :chart-height="500" :name="item.name" :k-days="20" :dark-theme="true"></k-line-chart>
</n-popover>
</n-text></n-td>
<n-td><n-text :type="item.percent>0?'error':'success'">{{item.percent}}%</n-text></n-td>
<n-td><n-text type="info">{{item.current}}</n-text></n-td>
<n-td><n-text type="info">{{item.value}}</n-text></n-td>
<n-td><n-text :type="item.increment>0?'error':'success'">
{{item.increment}}
<n-icon v-if="item.increment>0" :component="ArrowUp"/>
<n-icon v-else :component="ArrowDown"/>
</n-text></n-td>
<n-td>
<n-text :type="item.rank_change>0?'error':'success'">
{{item.rank_change}}
<n-icon v-if="item.rank_change>0" :component="ArrowUp"/>
<n-text v-else-if="item.rank_change==0" ></n-text>
<n-icon v-else :component="ArrowDown"/>
</n-text>
</n-td>
</n-tr>
</n-tbody>
</n-table>
</template>
<style scoped>
</style>

View File

@ -1,73 +0,0 @@
<script setup lang="ts">
import {onBeforeMount, onUnmounted, ref} from 'vue'
import {HotTopic} from "../../wailsjs/go/main/App";
const list = ref([])
const task =ref()
onBeforeMount(async () => {
list.value = await HotTopic(10)
setInterval(async ()=>{
list.value = await HotTopic(10)
}, 1000*10)
})
onUnmounted(()=>{
clearInterval(task.value)
})
function openCenteredWindow(url, width, height) {
const left = (window.screen.width - width) / 2;
const top = (window.screen.height - height) / 2;
return window.open(
url,
'centeredWindow',
`width=${width},height=${height},left=${left},top=${top}`
);
}
function showPage(htid) {
openCenteredWindow(`https://gubatopic.eastmoney.com/topic_v3.html?htid=${htid}`, 1000, 600)
}
</script>
<template>
<n-list bordered hoverable clickable>
<!-- <template #header>-->
<!-- 股吧热门-->
<!-- </template>-->
<n-list-item v-for="(item, index) in list" :key="index">
<n-thing :title="item.nickname" :description="item.desc" :description-style="'font-size: 14px;'" @click="showPage(item.htid)">
<template v-if="item.squareImg" #avatar>
<n-avatar :src="item.squareImg" :size="60">
</n-avatar>
</template>
<template v-if="item.stock_list" #footer>
<n-flex>
<n-tag type="info" v-for="(v, i) in item.stock_list" :bordered="false" size="small">
{{v.name}}
</n-tag>
</n-flex>
</template>
<template v-if="item.clickNumber" #header-extra>
<n-flex>
<n-button secondary type="warning" size="tiny">讨论数<n-number-animation
show-separator
:from="0"
:to="item.postNumber"
/>
</n-button >
<n-tag :bordered="false" type="warning" size="small">浏览量<n-number-animation
show-separator
:from="0"
:to="item.clickNumber"
/>
</n-tag>
</n-flex>
</template>
</n-thing>
</n-list-item>
</n-list>
</template>
<style scoped>
</style>

View File

@ -1,115 +0,0 @@
<script setup>
import {onBeforeMount, ref} from 'vue'
import {GetStockList, IndustryResearchReport,EMDictCode} from "../../wailsjs/go/main/App";
import {ArrowDownOutline, CaretDown, CaretUp, PulseOutline, Refresh, RefreshCircleSharp,} from "@vicons/ionicons5";
import {useMessage} from "naive-ui";
import {BrowserOpenURL} from "../../wailsjs/runtime";
const message=useMessage()
const list = ref([])
const options = ref([])
function getIndustryResearchReport(value) {
message.loading("正在刷新数据...")
IndustryResearchReport(value).then(result => {
console.log(result)
list.value = result
})
}
onBeforeMount(()=>{
getIndustryResearchReport('');
})
function ratingChangeName(ratingChange){
if(ratingChange===0){
return '调高'
}else if(ratingChange===1){
return '调低'
}else if(ratingChange===2){
return '首次'
}else if(ratingChange===3){
return '维持'
}else if (ratingChange===4){
return '无变化'
}else{
return ''
}
}
function openWin(code) {
BrowserOpenURL("https://pdf.dfcfw.com/pdf/H3_"+code+"_1.pdf?1749744888000.pdf")
}
function EMDictCodeList(keyVal){
if (keyVal){
EMDictCode('016').then(result => {
console.log(result)
options.value=result.filter((value,index,array) => value.bkName.includes(keyVal)||value.firstLetter.includes(keyVal)||value.bkCode.includes(keyVal)).map(item => {
return {
label: item.bkName+" - "+item.bkCode,
value: item.bkCode
}
})
})
}else{
getIndustryResearchReport('')
}
}
function handleSearch(value) {
getIndustryResearchReport(value)
}
</script>
<template>
<n-card>
<n-auto-complete :options="options" placeholder="请输入行业名称关键词搜索" clearable filterable :on-select="handleSearch" :on-update:value="EMDictCodeList" />
</n-card>
<n-table striped size="small">
<n-thead>
<n-tr>
<!-- <n-th>代码</n-th>-->
<!-- <n-th>名称</n-th>-->
<n-th>行业</n-th>
<n-th>标题</n-th>
<n-th>东财评级</n-th>
<n-th>评级变动</n-th>
<n-th>机构评级</n-th>
<n-th>分析师</n-th>
<n-th>机构</n-th>
<n-th> <n-flex justify="space-between">日期<n-icon @click="getIndustryResearchReport" color="#409EFF" :size="20" :component="RefreshCircleSharp"/></n-flex></n-th>
</n-tr>
</n-thead>
<n-tbody>
<n-tr v-for="item in list" :key="item.infoCode">
<!-- <n-td>{{item.stockCode}}</n-td>-->
<!-- <n-td :title="item.stockCode">-->
<!-- <n-popover trigger="hover" placement="right">-->
<!-- <template #trigger>-->
<!-- <n-tag type="info" :bordered="false">{{item.stockName}}</n-tag>-->
<!-- </template>-->
<!-- <k-line-chart style="width: 800px" :code="getmMarketCode(item.market,item.stockCode)" :chart-height="500" :name="item.stockName" :k-days="20" :dark-theme="true"></k-line-chart>-->
<!-- </n-popover>-->
<!-- </n-td>-->
<n-td><n-tag type="info" :bordered="false">{{item.industryName}}</n-tag></n-td>
<n-td>
<n-a type="info" @click="openWin(item.infoCode)"><n-text type="success">{{item.title}}</n-text></n-a>
</n-td>
<n-td><n-text :type="item.emRatingName==='增持'?'error':'info'">
{{item.emRatingName}}
</n-text></n-td>
<n-td><n-text :type="item.ratingChange===0?'error':'info'">{{ratingChangeName(item.ratingChange)}}</n-text></n-td>
<n-td>{{item.sRatingName }}</n-td>
<n-td><n-ellipsis style="max-width: 120px">{{item.researcher}}</n-ellipsis></n-td>
<n-td>{{item.orgSName}}</n-td>
<n-td>{{item.publishDate.substring(0,10)}}</n-td>
</n-tr>
</n-tbody>
</n-table>
</template>
<style scoped>
</style>

View File

@ -1,108 +0,0 @@
<script setup lang="ts">
import {nextTick, onBeforeMount, onMounted, onUnmounted, ref} from 'vue'
import {InvestCalendarTimeLine} from "../../wailsjs/go/main/App";
import { addMonths, format ,parse} from 'date-fns';
import { zhCN } from 'date-fns/locale';
import {useMessage} from 'naive-ui'
import {Star48Filled} from "@vicons/fluent";
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0'); // 0+1
const day = String(today.getDate()).padStart(2, '0');
// YYYY-MM-DD
const formattedDate = `${year}-${month}-${day}`;
const formattedYM = `${year}-${month}`;
const list = ref([])
const message=useMessage()
function goBackToday() {
setTimeout(() => {
nextTick(
() => {
const elementById = document.getElementById(formattedDate);
if (elementById) {
elementById.scrollIntoView({
behavior: 'auto',
block: 'start'
})
}
}
)
}, 500)
}
onBeforeMount(() => {
InvestCalendarTimeLine(formattedYM).then(res => {
list.value = res
goBackToday();
})
})
onMounted(()=>{
})
function loadMore(){
if (list.value.length>0){
let day=parse(list.value[list.value.length-1].date, 'yyyy-MM-dd', new Date())
let nextMonth=addMonths(day,1)
let ym = format(nextMonth, 'yyyy-MM');
console.log(ym)
InvestCalendarTimeLine(ym).then(res => {
if (res.length==0){
message.warning("没有更多数据了")
return
}
list.value.push( ...res)
})
}
}
function getweekday(date){
let day=parse(date, 'yyyy-MM-dd', new Date())
return format(day, 'EEEE', {locale: zhCN})
}
</script>
<template>
<!-- <n-timeline size="large" style="text-align: left">-->
<!-- <n-timeline-item v-for="item in list" :key="item.date" :title="item.date" type="info" >-->
<!-- <n-list>-->
<!-- <n-list-item v-for="l in item.list" :key="l.article_id ">-->
<!-- <n-text>{{l.title}}</n-text>-->
<!-- </n-list-item>-->
<!-- </n-list>-->
<!-- </n-timeline-item>-->
<!-- </n-timeline>-->
<n-list bordered style="max-height: calc(100vh - 230px);text-align: left;">
<n-scrollbar style="max-height: calc(100vh - 230px);" >
<n-list-item v-for="(item, index) in list" :id="item.date" :key="item.date">
<n-thing :title="item.date+' '+getweekday(item.date)">
<n-list :bordered="false" hoverable>
<n-list-item v-for="(l,i ) in item.list" :key="l.article_id ">
<n-flex justify="space-between">
<n-text :type="item.date===formattedDate?'warning':'info'">{{i+1}}# {{l.title}}</n-text>
<n-rate v-if="l.like_count>0" readonly :default-value="l.like_count" :count="l.like_count" >
<n-icon :component="Star48Filled"/>
</n-rate>
</n-flex>
</n-list-item>
</n-list>
</n-thing>
</n-list-item>
<n-list-item v-if="list.length==0">
<n-text type="info">没有数据</n-text>
</n-list-item>
<n-list-item v-else style="text-align: center;">
<n-button-group>
<n-button strong secondary type="info" @click="loadMore">加载更多</n-button>
<n-button strong secondary type="warning" @click="goBackToday">回到今天</n-button>
</n-button-group>
</n-list-item>
</n-scrollbar>
</n-list>
</template>
<style scoped>
</style>

View File

@ -60,7 +60,7 @@ function handleKLine(code,name){
////console.log("values",values) ////console.log("values",values)
let option = { let option = {
title: { title: {
text: name+" "+code, text: name,
left: '20px', left: '20px',
textStyle: { textStyle: {
color: darkTheme?'#ccc':'#456' color: darkTheme?'#ccc':'#456'
@ -96,7 +96,7 @@ function handleKLine(code,name){
color: darkTheme?'#ccc':'#456' color: darkTheme?'#ccc':'#456'
}, },
formatter: function (params) {// formatter: function (params) {//
//console.log("params",params) console.log("params",params)
let currentItemData = _.filter(params, (param) => param.seriesIndex === 0)[0].data; let currentItemData = _.filter(params, (param) => param.seriesIndex === 0)[0].data;
let ma5=_.filter(params, (param) => param.seriesIndex === 1)[0].data;//ma5 let ma5=_.filter(params, (param) => param.seriesIndex === 1)[0].data;//ma5
let ma10=_.filter(params, (param) => param.seriesIndex === 2)[0].data;//ma10 let ma10=_.filter(params, (param) => param.seriesIndex === 2)[0].data;//ma10
@ -354,10 +354,6 @@ function handleKLine(code,name){
] ]
}; };
chart.setOption(option); chart.setOption(option);
chart.on('click',{seriesName:'日K'}, function(params) {
//console.log("click:",params);
});
}) })
} }
function calculateMA(dayCount,values) { function calculateMA(dayCount,values) {

View File

@ -1,229 +0,0 @@
<script setup lang="ts">
import {onBeforeMount, ref} from 'vue'
import {LongTigerRank} from "../../wailsjs/go/main/App";
import {BrowserOpenURL} from "../../wailsjs/runtime";
import {ArrowDownOutline} from "@vicons/ionicons5";
import _ from "lodash";
import KLineChart from "./KLineChart.vue";
import MoneyTrend from "./moneyTrend.vue";
import {NButton, NText, useMessage} from "naive-ui";
const message = useMessage()
const lhbList= ref([])
const EXPLANATIONs = ref([])
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0'); // 0+1
const day = String(today.getDate()).padStart(2, '0');
// YYYY-MM-DD
const formattedDate = `${year}-${month}-${day}`;
const SearchForm= ref({
dateValue: formattedDate,
EXPLANATION:null,
})
onBeforeMount(() => {
longTiger(formattedDate);
})
function longTiger_old(date) {
if(date) {
SearchForm.value.dateValue = date
}
let loading1=message.loading("正在获取龙虎榜数据...",{
duration: 0,
})
LongTigerRank(date).then(res => {
lhbList.value = res
loading1.destroy()
if (res.length === 0) {
message.info("暂无数据,请切换日期")
}
EXPLANATIONs.value=_.uniqBy(_.map(lhbList.value,function (item){
return {
label: item['EXPLANATION'],
value: item['EXPLANATION'],
}
}),'label');
})
}
function longTiger(date) {
if (date) {
SearchForm.value.dateValue = date;
}
let loading1 = message.loading("正在获取龙虎榜数据...", {
duration: 0,
});
const fetchDate = (currentDate, retryCount = 0) => {
if (retryCount > 7) { // 7
lhbList.value = [];
EXPLANATIONs.value = [];
loading1.destroy();
message.info("暂无历史数据");
return;
}
LongTigerRank(currentDate).then(res => {
if (res.length === 0) {
const previousDate = new Date(currentDate);
previousDate.setDate(previousDate.getDate() - 1);
const year = previousDate.getFullYear();
const month = String(previousDate.getMonth() + 1).padStart(2, '0');
const day = String(previousDate.getDate()).padStart(2, '0');
const prevFormattedDate = `${year}-${month}-${day}`;
message.info(`当前日期 ${currentDate} 暂无数据,尝试查询前一日:${prevFormattedDate}`);
SearchForm.value.dateValue = prevFormattedDate;
fetchDate(prevFormattedDate, retryCount + 1); //
} else {
lhbList.value = res;
loading1.destroy();
EXPLANATIONs.value = _.uniqBy(_.map(lhbList.value, function (item) {
return {
label: item['EXPLANATION'],
value: item['EXPLANATION'],
};
}), 'label');
}
}).catch(err => {
loading1.destroy();
message.error("获取数据失败,请重试");
console.error(err);
});
};
fetchDate(date || formattedDate);
}
function handleEXPLANATION(value, option){
SearchForm.value.EXPLANATION = value
if(value){
LongTigerRank(SearchForm.value.dateValue).then(res => {
lhbList.value=_.filter(res, function(o) { return o['EXPLANATION']===value; });
if (res.length === 0) {
message.info("暂无数据,请切换日期")
}
})
}else{
longTiger(SearchForm.value.dateValue)
}
}
</script>
<template>
<n-form :model="SearchForm" >
<n-grid :cols="24" :x-gap="24">
<n-form-item-gi :span="4" label="日期" path="dateValue" label-placement="left">
<n-date-picker v-model:formatted-value="SearchForm.dateValue"
value-format="yyyy-MM-dd" type="date" :on-update:value="(v,v2)=>longTiger(v2)"/>
</n-form-item-gi>
<n-form-item-gi :span="8" label="上榜原因" path="EXPLANATION" label-placement="left">
<n-select clearable placeholder="上榜原因过滤" v-model:value="SearchForm.EXPLANATION" :options="EXPLANATIONs" :on-update:value="handleEXPLANATION"/>
</n-form-item-gi>
<n-form-item-gi :span="10" label="" label-placement="left">
<n-text type="error">*当天的龙虎榜数据通常在收盘结束后一小时左右更新</n-text>
</n-form-item-gi>
</n-grid>
</n-form>
<n-table :single-line="false" striped>
<n-thead>
<n-tr>
<n-th>代码</n-th>
<!-- <n-th width="90px">日期</n-th>-->
<n-th width="60px">名称</n-th>
<n-th>收盘价</n-th>
<n-th width="60px">涨跌幅</n-th>
<n-th>龙虎榜净买额()</n-th>
<n-th>龙虎榜买入额()</n-th>
<n-th>龙虎榜卖出额()</n-th>
<n-th>龙虎榜成交额()</n-th>
<!-- <n-th>市场总成交额()</n-th>-->
<!-- <n-th>净买额占总成交比</n-th>-->
<!-- <n-th>成交额占总成交比</n-th>-->
<n-th width="60px" data-field="TURNOVERRATE">换手率<n-icon :component="ArrowDownOutline" /></n-th>
<n-th>流通市值(亿)</n-th>
<n-th>上榜原因</n-th>
<!-- <n-th>解读</n-th>-->
</n-tr>
</n-thead>
<n-tbody>
<n-tr v-for="(item, index) in lhbList" :key="index">
<n-td>
<n-tag :bordered=false type="info">{{ item.SECUCODE.split('.')[1].toLowerCase()+item.SECUCODE.split('.')[0] }}</n-tag>
</n-td>
<!-- <n-td>
{{item.TRADE_DATE.substring(0,10)}}
</n-td>-->
<n-td>
<!-- <n-text :type="item.CHANGE_RATE>0?'error':'success'">{{ item.SECURITY_NAME_ABBR }}</n-text>-->
<n-popover trigger="hover" placement="right">
<template #trigger>
<n-button tag="a" text :type="item.CHANGE_RATE>0?'error':'success'" :bordered=false >{{ item.SECURITY_NAME_ABBR }}</n-button>
</template>
<k-line-chart style="width: 800px" :code="item.SECUCODE.split('.')[1].toLowerCase()+item.SECUCODE.split('.')[0]" :chart-height="500" :name="item.SECURITY_NAME_ABBR" :k-days="20" :dark-theme="true"></k-line-chart>
</n-popover>
</n-td>
<n-td>
<n-text :type="item.CHANGE_RATE>0?'error':'success'">{{ item.CLOSE_PRICE }}</n-text>
</n-td>
<n-td>
<n-text :type="item.CHANGE_RATE>0?'error':'success'">{{ (item.CHANGE_RATE).toFixed(2) }}%</n-text>
</n-td>
<n-td>
<!-- <n-text :type="item.BILLBOARD_NET_AMT>0?'error':'success'">{{ (item.BILLBOARD_NET_AMT/10000).toFixed(2) }}</n-text>-->
<n-popover trigger="hover" placement="right">
<template #trigger>
<n-button tag="a" text :type="item.BILLBOARD_NET_AMT>0?'error':'success'" :bordered=false >{{ (item.BILLBOARD_NET_AMT/10000).toFixed(2) }}</n-button>
</template>
<money-trend :code="item.SECUCODE.split('.')[1].toLowerCase()+item.SECUCODE.split('.')[0]" :name="item.SECURITY_NAME_ABBR" :days="360" :dark-theme="true" :chart-height="500" style="width: 800px"></money-trend>
</n-popover>
</n-td>
<n-td>
<n-text :type="'error'">{{ (item.BILLBOARD_BUY_AMT/10000).toFixed(2) }}</n-text>
</n-td>
<n-td>
<n-text :type="'success'">{{ (item.BILLBOARD_SELL_AMT/10000).toFixed(2) }}</n-text>
</n-td>
<n-td>
<n-text :type="'info'">{{ (item.BILLBOARD_DEAL_AMT/10000).toFixed(2) }}</n-text>
</n-td>
<!-- <n-td>-->
<!-- <n-text :type="'info'">{{ (item.ACCUM_AMOUNT/10000).toFixed(2) }}</n-text>-->
<!-- </n-td>-->
<!-- <n-td>-->
<!-- <n-text :type="item.DEAL_NET_RATIO>0?'error':'success'">{{ (item.DEAL_NET_RATIO).toFixed(2) }}%</n-text>-->
<!-- </n-td>-->
<!-- <n-td>-->
<!-- <n-text :type="'info'">{{ (item.DEAL_AMOUNT_RATIO).toFixed(2) }}%</n-text>-->
<!-- </n-td>-->
<n-td>
<n-text :type="'info'">{{ (item.TURNOVERRATE).toFixed(2) }}%</n-text>
</n-td>
<n-td>
<n-text :type="'info'">{{ (item.FREE_MARKET_CAP/100000000).toFixed(2) }}</n-text>
</n-td>
<n-td>
<n-text :type="'info'">{{ item.EXPLANATION }}</n-text>
</n-td>
<!-- <n-td>
<n-text :type="item.CHANGE_RATE>0?'error':'success'">{{ item.EXPLAIN }}</n-text>
</n-td>-->
</n-tr>
</n-tbody>
</n-table>
</template>
<style scoped>
</style>

View File

@ -1,126 +0,0 @@
<script setup lang="ts">
import {h, onBeforeMount, onMounted, onUnmounted, ref} from 'vue'
import {SearchStock} from "../../wailsjs/go/main/App";
import {useMessage, NText, NTag} from 'naive-ui'
const message = useMessage()
const search = ref('科技股;换手率连续3日大于2')
const columns = ref([])
const dataList = ref([])
function Search() {
const loading = message.loading("正在获取选股数据...", {duration: 0});
SearchStock(search.value).then(res => {
loading.destroy()
//console.log(res)
if(res.code==100){
message.success(res.msg)
columns.value=res.data.result.columns.filter(item=>!item.hiddenNeed&&(item.title!="市场码"&&item.title!="市场简称")).map(item=>{
if(item.children){
return {
title:item.title+(item.unit?'['+item.unit+']':''),
key:item.key,
resizable: true,
minWidth:200,
ellipsis: {
tooltip: true
},
children:item.children.filter(item=>!item.hiddenNeed).map(item=>{
return {
title:item.dateMsg,
key:item.key,
minWidth:100,
resizable: true,
ellipsis: {
tooltip: true
}
}
})
}
}else{
return {
title:item.title+(item.unit?'['+item.unit+']':''),
key:item.key,
resizable: true,
minWidth:100,
ellipsis: {
tooltip: true
}
}
}
})
dataList.value=res.data.result.dataList
}else {
message.error(res.msg)
}
}).catch(err => {
message.error(err)
})
}
function isNumeric(value) {
return !isNaN(parseFloat(value)) && isFinite(value);
}
onBeforeMount(() => {
Search()
})
</script>
<template>
<n-flex>
<n-input-group>
<n-input v-model:value="search" placeholder="请输入选股指标或者要求" />
<n-button type="success" @click="Search">搜索A股</n-button>
</n-input-group>
</n-flex>
<!-- <n-table striped size="small">-->
<!-- <n-thead>-->
<!-- <n-tr>-->
<!-- <n-th v-for="item in columns">{{item.title}}</n-th>-->
<!-- </n-tr>-->
<!-- </n-thead>-->
<!-- <n-tbody>-->
<!-- <n-tr v-for="(item,index) in dataList">-->
<!-- <n-td v-for="d in columns">{{item[d.key]}}</n-td>-->
<!-- </n-tr>-->
<!-- </n-tbody>-->
<!-- </n-table>-->
<n-data-table
:max-height="'calc(100vh - 285px)'"
size="small"
:columns="columns"
:data="dataList"
:pagination="false"
:scroll-x="1800"
:render-cell="(value, rowData, column) => {
if(column.key=='SECURITY_CODE'||column.key=='SERIAL'){
return h(NText, { type: 'info',border: false }, { default: () => `${value}` })
}
if (isNumeric(value)) {
let type='info';
if (Number(value)<0){
type='success';
}
if(Number(value)>=0&&Number(value)<=5){
type='warning';
}
if (Number(value)>5){
type='error';
}
return h(NText, { type: type }, { default: () => `${value}` })
}else{
if(column.key=='SECURITY_SHORT_NAME'){
return h(NTag, { type: 'info',bordered: false }, { default: () => `${value}` })
}else{
return h(NText, { type: 'info' }, { default: () => `${value}` })
}
}
}"
/>
</template>
<style scoped>
</style>

View File

@ -1,148 +0,0 @@
<script setup lang="ts">
import {onBeforeMount, ref} from 'vue'
import {GetStockList, StockNotice} from "../../wailsjs/go/main/App";
import {BrowserOpenURL} from "../../wailsjs/runtime";
import {RefreshCircleSharp} from "@vicons/ionicons5";
import _ from "lodash";
import KLineChart from "./KLineChart.vue";
import MoneyTrend from "./moneyTrend.vue";
import {useMessage} from "naive-ui";
const {stockCode}=defineProps(
{
stockCode: {
type: String,
default: ''
}
}
)
const list = ref([])
const options = ref([])
const message=useMessage()
function getNotice(stockCodes) {
StockNotice(stockCodes).then(result => {
console.log(result)
list.value = result
})
}
onBeforeMount (()=>{
//message.info(""+stockCode)
getNotice(stockCode);
})
function findStockList(query){
if (query){
GetStockList(query).then(result => {
options.value=result.map(item => {
return {
label: item.name+" - "+item.ts_code,
value: item.ts_code
}
})
})
}else{
getNotice("")
}
}
function handleSearch(value) {
getNotice(value)
}
function openWin(code) {
BrowserOpenURL("https://pdf.dfcfw.com/pdf/H2_"+code+"_1.pdf?1750092081000.pdf")
}
function getTypeColor(name){
if(name.includes("质押")||name.includes("冻结")||name.includes("解冻")||name.includes("解押")||name.includes("解禁")){
return "error"
}
if(name.includes("异常")||name.includes("减持")||name.includes("增发")||name.includes("重大")){
return "error"
}
if(name.includes("季度报告")||name.includes("年度报告")||name.includes("澄清公告")||name.includes("风险")){
return "error"
}
if(name.includes("终止")||name.includes("复牌")||name.includes("停牌")||name.includes("退市")){
return "error"
}
if(name.includes("破产")||name.includes("清算")){
return "error"
}
if(name.includes("回购")||name.includes("重组")||name.includes("诉讼")||name.includes("仲裁")||name.includes("转让")||name.includes("收购")){
return "warning"
}
if(name.includes("调研")||name.includes("募集")){
return "warning"
}
return "info"
}
function getmMarketCode(market,code) {
if(market==="0"){
return "sz"+code
}else if(market==="1"){
return "sh"+code
}else if(market==="2"){
return "bj"+code
}else if(market==="3"){
return "hk"+code
}else{
return code
}
}
</script>
<template>
<n-card>
<n-auto-complete :options="options" placeholder="请输入A股名称或者代码" clearable filterable :on-select="handleSearch" :on-update:value="findStockList" />
</n-card>
<n-table striped size="small">
<n-thead>
<n-tr>
<n-th>股票代码</n-th>
<n-th>股票名称</n-th>
<n-th>公告标题</n-th>
<n-th>公告类型</n-th>
<n-th>公告日期</n-th>
<n-th><n-flex>数据更新时间<n-icon @click="getNotice('')" color="#409EFF" :size="20" :component="RefreshCircleSharp"/></n-flex></n-th>
</n-tr>
</n-thead>
<n-tbody>
<n-tr v-for="item in list" :key="item.art_code">
<n-td>
<n-popover trigger="hover" placement="right">
<template #trigger>
<n-tag type="info" :bordered="false">{{item.codes[0].stock_code }}</n-tag>
</template>
<money-trend style="width: 800px" :code="getmMarketCode(item.codes[0].market_code,item.codes[0].stock_code)" :name="item.codes[0].short_name" :days="360" :dark-theme="true" :chart-height="500"></money-trend>
</n-popover>
</n-td>
<n-td>
<n-popover trigger="hover" placement="right">
<template #trigger>
<n-tag type="info" :bordered="false">{{item.codes[0].short_name }}</n-tag>
</template>
<k-line-chart style="width: 800px" :code="getmMarketCode(item.codes[0].market_code,item.codes[0].stock_code)" :chart-height="500" :name="item.codes[0].short_name" :k-days="20" :dark-theme="true"></k-line-chart>
</n-popover>
</n-td>
<n-td>
<n-a type="info" @click="openWin(item.art_code)"><n-text :type="getTypeColor(item.columns[0].column_name)"> {{item.title}}</n-text></n-a>
</n-td>
<n-td>
<n-text :type="getTypeColor(item.columns[0].column_name)">{{item.columns[0].column_name }}</n-text>
</n-td>
<n-td>
<n-tag type="info">{{item.notice_date.substring(0,10) }}</n-tag>
</n-td>
<n-td>
<n-tag type="info">{{item.display_time.substring(0,19)}}</n-tag>
</n-td>
</n-tr>
</n-tbody>
</n-table>
</template>
<style scoped>
</style>

View File

@ -1,136 +0,0 @@
<script setup>
import {onBeforeMount, ref} from 'vue'
import {GetStockList, StockResearchReport} from "../../wailsjs/go/main/App";
import {ArrowDownOutline, CaretDown, CaretUp, PulseOutline, Refresh, RefreshCircleSharp,} from "@vicons/ionicons5";
import KLineChart from "./KLineChart.vue";
import MoneyTrend from "./moneyTrend.vue";
import {useMessage} from "naive-ui";
import {BrowserOpenURL} from "../../wailsjs/runtime";
const {stockCode}=defineProps(
{
stockCode: {
type: String,
default: ''
}
}
)
const message=useMessage()
const list = ref([])
const options = ref([])
function getStockResearchReport(value) {
StockResearchReport(value).then(result => {
//console.log(result)
list.value = result
})
}
onBeforeMount(()=>{
getStockResearchReport(stockCode);
})
function ratingChangeName(ratingChange){
if(ratingChange===0){
return '调高'
}else if(ratingChange===1){
return '调低'
}else if(ratingChange===2){
return '首次'
}else if(ratingChange===3){
return '维持'
}else if (ratingChange===4){
return '无变化'
}else{
return ''
}
}
function getmMarketCode(market,code) {
if(market==="SHENZHEN"){
return "sz"+code
}else if(market==="SHANGHAI"){
return "sh"+code
}else if(market==="BEIJING"){
return "bj"+code
}else if(market==="HONGKONG"){
return "hk"+code
}else{
return code
}
}
function openWin(code) {
BrowserOpenURL("https://pdf.dfcfw.com/pdf/H3_"+code+"_1.pdf?1749744888000.pdf")
}
function findStockList(query){
if (query){
GetStockList(query).then(result => {
options.value=result.map(item => {
return {
label: item.name+" - "+item.ts_code,
value: item.ts_code
}
})
})
}else{
getStockResearchReport('')
}
}
function handleSearch(value) {
getStockResearchReport(value)
}
</script>
<template>
<n-card>
<n-auto-complete :options="options" placeholder="请输入A股名称或者代码" clearable filterable :on-select="handleSearch" :on-update:value="findStockList" />
</n-card>
<n-table striped size="small">
<n-thead>
<n-tr>
<!-- <n-th>代码</n-th>-->
<n-th>名称</n-th>
<n-th>行业</n-th>
<n-th>标题</n-th>
<n-th>东财评级</n-th>
<n-th>评级变动</n-th>
<n-th>机构评级</n-th>
<n-th>分析师</n-th>
<n-th>机构</n-th>
<n-th> <n-flex justify="space-between">日期<n-icon @click="getStockResearchReport" color="#409EFF" :size="20" :component="RefreshCircleSharp"/></n-flex></n-th>
</n-tr>
</n-thead>
<n-tbody>
<n-tr v-for="item in list" :key="item.infoCode">
<!-- <n-td>{{item.stockCode}}</n-td>-->
<n-td :title="item.stockCode">
<n-popover trigger="hover" placement="right">
<template #trigger>
<n-tag type="info" :bordered="false">{{item.stockName}}</n-tag>
</template>
<k-line-chart style="width: 800px" :code="getmMarketCode(item.market,item.stockCode)" :chart-height="500" :name="item.stockName" :k-days="20" :dark-theme="true"></k-line-chart>
</n-popover>
</n-td>
<n-td><n-tag type="info" :bordered="false">{{item.indvInduName}}</n-tag></n-td>
<n-td>
<n-a type="info" @click="openWin(item.infoCode)">{{item.title}}</n-a>
</n-td>
<n-td><n-text :type="item.emRatingName==='增持'?'error':'info'">
{{item.emRatingName}}
</n-text></n-td>
<n-td><n-text :type="item.ratingChange===0?'error':'info'">{{ratingChangeName(item.ratingChange)}}</n-text></n-td>
<n-td>{{item.sRatingName}}</n-td>
<n-td>{{item.researcher}}</n-td>
<n-td>{{item.orgSName}}</n-td>
<n-td>{{item.publishDate.substring(0,10)}}</n-td>
</n-tr>
</n-tbody>
</n-table>
</template>
<style scoped>
</style>

View File

@ -4,7 +4,7 @@
import 'md-editor-v3/lib/preview.css'; import 'md-editor-v3/lib/preview.css';
import {h, onBeforeUnmount, onMounted, ref} from 'vue'; import {h, onBeforeUnmount, onMounted, ref} from 'vue';
import {CheckUpdate, GetVersionInfo} from "../../wailsjs/go/main/App"; import {CheckUpdate, GetVersionInfo} from "../../wailsjs/go/main/App";
import {EventsOff, EventsOn} from "../../wailsjs/runtime"; import {EventsOn} from "../../wailsjs/runtime";
import {NAvatar, NButton, useNotification} from "naive-ui"; import {NAvatar, NButton, useNotification} from "naive-ui";
const updateLog = ref(''); const updateLog = ref('');
const versionInfo = ref(''); const versionInfo = ref('');
@ -25,7 +25,6 @@ onMounted(() => {
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
notify.destroyAll() notify.destroyAll()
EventsOff("updateVersion")
}) })
EventsOn("updateVersion",async (msg) => { EventsOn("updateVersion",async (msg) => {
@ -44,8 +43,8 @@ EventsOn("updateVersion",async (msg) => {
const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
//console.log("GitHub UTC :", utcDate); console.log("GitHub UTC 时间:", utcDate);
//console.log(":", formattedDate); console.log("转换后的本地时间:", formattedDate);
notify.info({ notify.info({
avatar: () => avatar: () =>
h(NAvatar, { h(NAvatar, {
@ -135,7 +134,6 @@ EventsOn("updateVersion",async (msg) => {
</p> </p>
<p> <p>
感谢以下开发者 感谢以下开发者
<a href="https://github.com/CodeNoobLH" target="_blank">浓睡不消残酒</a><n-divider vertical />
<a href="https://github.com/gnim2600" target="_blank">@gnim2600</a><n-divider vertical /> <a href="https://github.com/gnim2600" target="_blank">@gnim2600</a><n-divider vertical />
<a href="https://github.com/XXXiaohuayanGGG" target="_blank">@XXXiaohuayanGGG</a><n-divider vertical /> <a href="https://github.com/XXXiaohuayanGGG" target="_blank">@XXXiaohuayanGGG</a><n-divider vertical />
<a href="https://github.com/2lovecode" target="_blank">@2lovecode</a><n-divider vertical /> <a href="https://github.com/2lovecode" target="_blank">@2lovecode</a><n-divider vertical />

View File

@ -47,7 +47,7 @@ onBeforeMount(()=>{
}) })
GetFollowedFund().then(result => { GetFollowedFund().then(result => {
followList.value = result followList.value = result
//console.log("followList",followList.value) console.log("followList",followList.value)
}) })
}) })
@ -60,7 +60,7 @@ onMounted(() => {
//ws.value = new WebSocket('ws://localhost:16688/ws'); // WebSocket //ws.value = new WebSocket('ws://localhost:16688/ws'); // WebSocket
ws.value.onopen = () => { ws.value.onopen = () => {
//console.log('WebSocket '); console.log('WebSocket 连接已打开');
}; };
ws.value.onmessage = (event) => { ws.value.onmessage = (event) => {
@ -74,13 +74,13 @@ onMounted(() => {
}; };
ws.value.onclose = () => { ws.value.onclose = () => {
//console.log('WebSocket '); console.log('WebSocket 连接已关闭');
}; };
ticker.value=setInterval(() => { ticker.value=setInterval(() => {
GetFollowedFund().then(result => { GetFollowedFund().then(result => {
followList.value = result followList.value = result
//console.log("followList",followList.value) console.log("followList",followList.value)
}) })
}, 1000*60) }, 1000*60)
@ -103,7 +103,7 @@ function AddFund(){
message.success("关注成功") message.success("关注成功")
GetFollowedFund().then(result => { GetFollowedFund().then(result => {
followList.value = result followList.value = result
//console.log("followList",followList.value) console.log("followList",followList.value)
}) })
} }
}) })
@ -114,7 +114,7 @@ function unFollow(code){
message.success("取消关注成功") message.success("取消关注成功")
GetFollowedFund().then(result => { GetFollowedFund().then(result => {
followList.value = result followList.value = result
//console.log("followList",followList.value) console.log("followList",followList.value)
}) })
} }
}) })

View File

@ -1,94 +0,0 @@
<script setup>
import {CaretDown, CaretUp, RefreshCircleOutline} from "@vicons/ionicons5";
import {NText,useMessage} from "naive-ui";
import {onBeforeUnmount, onMounted, onUnmounted, ref} from "vue";
import {GetIndustryMoneyRankSina} from "../../wailsjs/go/main/App";
import KLineChart from "./KLineChart.vue";
const props = defineProps({
headerTitle: {
type: String,
default: '行业资金排名(净流入)'
},
fenlei: {
type: String,
default: '0'
},
sort: {
type: String,
default: 'netamount'
},
})
const message = useMessage()
const dataList= ref([])
const sort = ref(props.sort)
const fenlei= ref(props.fenlei)
const interval = ref(null)
onMounted(()=>{
sort.value=props.sort
fenlei.value=props.fenlei
GetRankData()
interval.value=setInterval(()=>{
GetRankData()
},1000*60)
})
onBeforeUnmount(()=>{
clearInterval(interval.value)
})
function GetRankData(){
message.loading("正在刷新数据...")
GetIndustryMoneyRankSina(fenlei.value,sort.value).then(result => {
if(result.length>0){
dataList.value = result
//console.log(result)
}
})
}
</script>
<template>
<n-table striped size="small">
<n-thead>
<n-tr>
<n-th>板块名称</n-th>
<n-th>涨跌幅</n-th>
<n-th>流入资金/</n-th>
<n-th>流出资金/</n-th>
<n-th>净流入/<n-icon v-if="sort==='0'" :component="CaretDown"/><n-icon v-if="sort==='1'" :component="CaretUp"/></n-th>
<n-th>净流入率</n-th>
<n-th>领涨股</n-th>
<n-th>涨跌幅</n-th>
<n-th>最新价</n-th>
<n-th>净流入率</n-th>
</n-tr>
</n-thead>
<n-tbody>
<n-tr v-for="item in dataList" :key="item.category">
<n-td><n-tag :bordered=false type="info">{{item.name}}</n-tag></n-td>
<n-td> <n-text :type="item.avg_changeratio>0?'error':'success'">{{(item.avg_changeratio*100).toFixed(2)}}%</n-text></n-td>
<n-td><n-text type="info">{{(item.inamount/10000).toFixed(2)}}</n-text></n-td>
<n-td><n-text type="info">{{(item.outamount/10000).toFixed(2)}}</n-text></n-td>
<n-td><n-text :type="item.netamount>0?'error':'success'">{{(item.netamount/10000).toFixed(2)}}</n-text></n-td>
<n-td><n-text :type="item.ratioamount>0?'error':'success'">{{(item.ratioamount*100).toFixed(2)}}%</n-text></n-td>
<n-td>
<!-- <n-text type="info">{{item.ts_name}}</n-text>-->
<n-popover trigger="hover" placement="right">
<template #trigger>
<n-button tag="a" text :type="item.ts_changeratio>0?'error':'success'" :bordered=false >{{ item.ts_name }}</n-button>
</template>
<k-line-chart style="width: 800px" :code="item.ts_symbol" :chart-height="500" :name="item.ts_name" :k-days="20" :dark-theme="true"></k-line-chart>
</n-popover>
</n-td>
<n-td><n-text :type="item.ts_changeratio>0?'error':'success'">{{(item.ts_changeratio*100).toFixed(2)}}%</n-text></n-td>
<n-td><n-text type="info">{{item.ts_trade}}</n-text></n-td>
<n-td><n-text :type="item.ts_ratioamount>0?'error':'success'">{{(item.ts_ratioamount*100).toFixed(2)}}%</n-text></n-td>
</n-tr>
</n-tbody>
</n-table>
</template>
<style scoped>
</style>

View File

@ -1,39 +1,20 @@
<script setup> <script setup>
import {computed, h, onBeforeMount, onBeforeUnmount, ref} from 'vue' import {computed, h, onBeforeMount, ref} from 'vue'
import { import {
GetAIResponseResult, GetAIResponseResult,
GetConfig, GetConfig, GetPromptTemplates,
GetIndustryRank,
GetPromptTemplates,
GetTelegraphList, GetTelegraphList,
GlobalStockIndexes, GlobalStockIndexes,
ReFleshTelegraphList, SaveAIResponseResult, SaveAsMarkdown, ShareAnalysis,
SaveAIResponseResult,
SaveAsMarkdown,
ShareAnalysis,
SummaryStockNews SummaryStockNews
} from "../../wailsjs/go/main/App"; } from "../../wailsjs/go/main/App";
import {EventsOff, EventsOn} from "../../wailsjs/runtime"; import {EventsOn} from "../../wailsjs/runtime";
import NewsList from "./newsList.vue"; import NewsList from "./newsList.vue";
import KLineChart from "./KLineChart.vue"; import KLineChart from "./KLineChart.vue";
import { CaretDown, CaretUp, PulseOutline,} from "@vicons/ionicons5"; import {Add, ChatboxOutline, PulseOutline,} from "@vicons/ionicons5";
import {NAvatar, NButton, NFlex, NText, useMessage, useNotification} from "naive-ui"; import {NAvatar, NButton, NFlex, NText, useMessage, useNotification} from "naive-ui";
import {MdPreview} from "md-editor-v3"; import {ExportPDF} from "@vavt/v3-extension";
import {useRoute} from 'vue-router' import {MdEditor, MdPreview} from "md-editor-v3";
import RankTable from "./rankTable.vue";
import IndustryMoneyRank from "./industryMoneyRank.vue";
import StockResearchReportList from "./StockResearchReportList.vue";
import StockNoticeList from "./StockNoticeList.vue";
import LongTigerRankList from "./LongTigerRankList.vue";
import IndustryResearchReportList from "./IndustryResearchReportList.vue";
import HotStockList from "./HotStockList.vue";
import HotEvents from "./HotEvents.vue";
import HotTopics from "./HotTopics.vue";
import InvestCalendarTimeLine from "./InvestCalendarTimeLine.vue";
import ClsCalendarTimeLine from "./ClsCalendarTimeLine.vue";
import SelectStock from "./SelectStock.vue";
const route = useRoute()
const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png'); const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png');
const message = useMessage() const message = useMessage()
@ -42,6 +23,7 @@ const panelHeight = ref(window.innerHeight - 240)
const telegraphList = ref([]) const telegraphList = ref([])
const sinaNewsList = ref([]) const sinaNewsList = ref([])
const common = ref([]) const common = ref([])
const america = ref([]) const america = ref([])
const europe = ref([]) const europe = ref([])
@ -64,12 +46,6 @@ const loading = ref(true)
const sysPromptOptions=ref([]) const sysPromptOptions=ref([])
const userPromptOptions=ref([]) const userPromptOptions=ref([])
const promptTemplates=ref([]) const promptTemplates=ref([])
const industryRanks = ref([])
const sort = ref("0")
const nowTab = ref("市场快讯")
const indexInterval = ref(null)
const indexIndustryRank = ref(null)
const stockCode= ref('')
function getIndex() { function getIndex() {
GlobalStockIndexes().then((res) => { GlobalStockIndexes().then((res) => {
@ -82,11 +58,7 @@ function getIndex() {
}) })
} }
onBeforeMount(() => { onBeforeMount(() => {
nowTab.value = route.query.name
stockCode.value = route.query.stockCode
GetConfig().then(result => { GetConfig().then(result => {
summaryBTN.value= result.openAiEnable summaryBTN.value= result.openAiEnable
darkTheme.value = result.darkTheme darkTheme.value = result.darkTheme
@ -104,29 +76,13 @@ onBeforeMount(() => {
sinaNewsList.value = res sinaNewsList.value = res
}) })
getIndex(); getIndex();
industryRank();
indexInterval.value = setInterval(() => { setInterval(() => {
getIndex() getIndex()
}, 3000) }, 3000)
indexIndustryRank.value = setInterval(() => {
industryRank()
}, 1000 * 10)
}) })
onBeforeUnmount(() => {
EventsOff("changeMarketTab")
EventsOff("newTelegraph")
EventsOff("newSinaNews")
EventsOff("summaryStockNews")
clearInterval(indexInterval.value)
clearInterval(indexIndustryRank.value)
})
EventsOn("changeMarketTab", async (msg) => {
//message.info(msg.name)
updateTab(msg.name)
})
EventsOn("newTelegraph", (data) => { EventsOn("newTelegraph", (data) => {
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
@ -149,7 +105,7 @@ window.onresize = () => {
function getAreaName(code){ function getAreaName(code){
switch (code) { switch (code) {
case "america": case "america":
return "美" return "美"
case "europe": case "europe":
return "欧洲" return "欧洲"
case "asia": case "asia":
@ -160,35 +116,12 @@ function getAreaName(code) {
return "其他" return "其他"
} }
} }
function changeIndustryRankSort() {
if (sort.value === "0") {
sort.value = "1"
} else {
sort.value = "0"
}
industryRank()
}
function industryRank() {
GetIndustryRank(sort.value, 150).then(result => {
if (result.length > 0) {
//console.log(result)
industryRanks.value = result
} else {
message.info("暂无数据")
}
})
}
function reAiSummary(){ function reAiSummary(){
aiSummary.value="" aiSummary.value=""
summaryModal.value = true summaryModal.value = true
loading.value = true loading.value = true
SummaryStockNews(question.value,sysPromptId.value) SummaryStockNews(question.value,sysPromptId.value)
} }
function getAiSummary(){ function getAiSummary(){
summaryModal.value = true summaryModal.value = true
loading.value = true loading.value = true
@ -218,7 +151,6 @@ function getAiSummary() {
function updateTab(name) { function updateTab(name) {
summaryBTN.value = (name === "市场快讯"); summaryBTN.value = (name === "市场快讯");
nowTab.value = name
} }
EventsOn("summaryStockNews",async (msg) => { EventsOn("summaryStockNews",async (msg) => {
@ -259,13 +191,11 @@ async function copyToClipboard() {
message.error('复制失败: ' + err); message.error('复制失败: ' + err);
} }
} }
function saveAsMarkdown(){ function saveAsMarkdown(){
SaveAsMarkdown('市场资讯','市场资讯').then(result => { SaveAsMarkdown('市场资讯','市场资讯').then(result => {
message.success(result) message.success(result)
}) })
} }
function share(){ function share(){
ShareAnalysis('市场资讯','市场资讯').then(msg => { ShareAnalysis('市场资讯','市场资讯').then(msg => {
//message.info(msg) //message.info(msg)
@ -289,30 +219,18 @@ function share() {
}) })
}) })
} }
function ReFlesh(source) {
//console.log("ReFlesh:", source)
ReFleshTelegraphList(source).then(res => {
if (source === "财联社电报") {
telegraphList.value = res
}
if (source === "新浪财经") {
sinaNewsList.value = res
}
})
}
</script> </script>
<template> <template>
<n-card> <n-card>
<n-tabs type="line" animated @update-value="updateTab" :value="nowTab"> <n-tabs type="line" animated @update-value="updateTab">
<n-tab-pane name="市场快讯" tab="市场快讯" > <n-tab-pane name="市场快讯" tab="市场快讯" >
<n-grid :cols="2" :y-gap="0"> <n-grid :cols="2" :y-gap="0">
<n-gi> <n-gi>
<news-list :newsList="telegraphList" :header-title="'财联社电报'" @update:message="ReFlesh"></news-list> <news-list :newsList="telegraphList" :header-title="'财联社电报'"></news-list>
</n-gi> </n-gi>
<n-gi> <n-gi>
<news-list :newsList="sinaNewsList" :header-title="'新浪财经'" @update:message="ReFlesh"></news-list> <news-list :newsList="sinaNewsList" :header-title="'新浪财经'"></news-list>
</n-gi> </n-gi>
</n-grid> </n-grid>
</n-tab-pane> </n-tab-pane>
@ -329,16 +247,11 @@ function ReFlesh(source) {
<n-grid :cols="3" :y-gap="0"> <n-grid :cols="3" :y-gap="0">
<n-gi> <n-gi>
<n-text :type="item.zdf>0?'error':'success'"> <n-text :type="item.zdf>0?'error':'success'"><n-image :src="item.img" width="20"/> &nbsp;{{ item.name }}</n-text>
<n-image :src="item.img" width="20"/> &nbsp;{{ item.name }}
</n-text>
</n-gi> </n-gi>
<n-gi> <n-gi>
<n-text :type="item.zdf>0?'error':'success'">{{ item.zxj }}</n-text>&nbsp; <n-text :type="item.zdf>0?'error':'success'">{{ item.zxj }}</n-text>&nbsp;
<n-text :type="item.zdf>0?'error':'success'"> <n-text :type="item.zdf>0?'error':'success'"><n-number-animation :precision="2" :from="0" :to="item.zdf" />%</n-text>
<n-number-animation :precision="2" :from="0" :to="item.zdf"/>
%
</n-text>
</n-gi> </n-gi>
<n-gi> <n-gi>
@ -413,195 +326,11 @@ function ReFlesh(source) {
<k-line-chart code="usYINN.AM" :chart-height="panelHeight" name="富时中国三倍做多" :k-days="20" <k-line-chart code="usYINN.AM" :chart-height="panelHeight" name="富时中国三倍做多" :k-days="20"
:dark-theme="true"></k-line-chart> :dark-theme="true"></k-line-chart>
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="VIX恐慌指数" tab="VIX恐慌指数">
<k-line-chart code="usUVXY.AM" :chart-height="panelHeight" name="VIX恐慌指数" :k-days="20"
:dark-theme="true"></k-line-chart>
</n-tab-pane>
</n-tabs> </n-tabs>
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="行业排名" tab="行业排名">
<n-tabs type="card" animated>
<n-tab-pane name="行业涨幅排名" tab="行业涨幅排名">
<n-table striped>
<n-thead>
<n-tr>
<n-th>行业名称</n-th>
<n-th @click="changeIndustryRankSort">行业涨幅
<n-icon v-if="sort==='0'" :component="CaretDown"/>
<n-icon v-if="sort==='1'" :component="CaretUp"/>
</n-th>
<n-th>行业5日涨幅</n-th>
<n-th>行业20日涨幅</n-th>
<n-th>领涨股</n-th>
<n-th>涨幅</n-th>
<n-th>最新价</n-th>
</n-tr>
</n-thead>
<n-tbody>
<n-tr v-for="item in industryRanks" :key="item.bd_code">
<n-td>
<n-tag :bordered=false type="info">{{ item.bd_name }}</n-tag>
</n-td>
<n-td>
<n-text :type="item.bd_zdf>0?'error':'success'">{{ item.bd_zdf }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.bd_zdf5>0?'error':'success'">{{ item.bd_zdf5 }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.bd_zdf20>0?'error':'success'">{{ item.bd_zdf20 }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.nzg_zdf>0?'error':'success'"> {{ item.nzg_name }}
<n-text type="info">{{ item.nzg_code }}</n-text>
</n-text>
</n-td>
<n-td>
<n-text :type="item.nzg_zdf>0?'error':'success'"> {{ item.nzg_zdf }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.nzg_zdf>0?'error':'success'">{{ item.nzg_zxj }}</n-text>
</n-td>
</n-tr>
</n-tbody>
</n-table>
<n-table striped>
<n-thead>
<n-tr>
<n-th>行业名称</n-th>
<n-th @click="changeIndustryRankSort">行业涨幅
<n-icon v-if="sort==='0'" :component="CaretDown"/>
<n-icon v-if="sort==='1'" :component="CaretUp"/>
</n-th>
<n-th>行业5日涨幅</n-th>
<n-th>行业20日涨幅</n-th>
<n-th>领涨股</n-th>
<n-th>涨幅</n-th>
<n-th>最新价</n-th>
</n-tr>
</n-thead>
<n-tbody>
<n-tr v-for="item in industryRanks" :key="item.bd_code">
<n-td>
<n-tag :bordered=false type="info">{{ item.bd_name }}</n-tag>
</n-td>
<n-td>
<n-text :type="item.bd_zdf>0?'error':'success'">{{ item.bd_zdf }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.bd_zdf5>0?'error':'success'">{{ item.bd_zdf5 }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.bd_zdf20>0?'error':'success'">{{ item.bd_zdf20 }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.nzg_zdf>0?'error':'success'"> {{ item.nzg_name }}
<n-text type="info">{{ item.nzg_code }}</n-text>
</n-text>
</n-td>
<n-td>
<n-text :type="item.nzg_zdf>0?'error':'success'"> {{ item.nzg_zdf }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.nzg_zdf>0?'error':'success'">{{ item.nzg_zxj }}</n-text>
</n-td>
</n-tr>
</n-tbody>
</n-table>
</n-tab-pane>
<n-tab-pane name="行业资金排名(净流入)" tab="行业资金排名">
<industryMoneyRank :fenlei="'0'" :header-title="'行业资金排名(净流入)'" :sort="'netamount'"/>
</n-tab-pane>
<n-tab-pane name="证监会行业资金排名(净流入)" tab="证监会行业资金排名">
<industryMoneyRank :fenlei="'2'" :header-title="'证监会行业资金排名(净流入)'" :sort="'netamount'"/>
</n-tab-pane>
<n-tab-pane name="概念板块资金排名(净流入)" tab="概念板块资金排名">
<industryMoneyRank :fenlei="'1'" :header-title="'概念板块资金排名(净流入)'" :sort="'netamount'"/>
</n-tab-pane>
</n-tabs>
</n-tab-pane>
<n-tab-pane name="个股资金流向" tab="个股资金流向">
<n-tabs type="card" animated>
<n-tab-pane name="netamount" tab="净流入额排名">
<RankTable :header-title="'净流入额排名'" :sort="'netamount'"/>
</n-tab-pane>
<n-tab-pane name="outamount" tab="流出资金排名">
<RankTable :header-title="'流出资金排名'" :sort="'outamount'"/>
</n-tab-pane>
<n-tab-pane name="ratioamount" tab="净流入率排名">
<RankTable :header-title="'净流入率排名'" :sort="'ratioamount'"/>
</n-tab-pane>
<n-tab-pane name="r0_net" tab="主力净流入额排名">
<RankTable :header-title="'主力净流入额排名'" :sort="'r0_net'"/>
</n-tab-pane>
<n-tab-pane name="r0_out" tab="主力流出排名">
<RankTable :header-title="'主力流出排名'" :sort="'r0_out'"/>
</n-tab-pane>
<n-tab-pane name="r0_ratio" tab="主力净流入率排名">
<RankTable :header-title="'主力净流入率排名'" :sort="'r0_ratio'"/>
</n-tab-pane>
<n-tab-pane name="r3_net" tab="散户净流入额排名">
<RankTable :header-title="'散户净流入额排名'" :sort="'r3_net'"/>
</n-tab-pane>
<n-tab-pane name="r3_out" tab="散户流出排名">
<RankTable :header-title="'散户流出排名'" :sort="'r3_out'"/>
</n-tab-pane>
<n-tab-pane name="r3_ratio" tab="散户净流入率排名">
<RankTable :header-title="'散户净流入率排名'" :sort="'r3_ratio'"/>
</n-tab-pane>
</n-tabs>
</n-tab-pane>
<n-tab-pane name="龙虎榜" tab="龙虎榜">
<LongTigerRankList />
</n-tab-pane>
<n-tab-pane name="个股研报" tab="个股研报">
<StockResearchReportList :stock-code="stockCode"/>
</n-tab-pane>
<n-tab-pane name="公司公告" tab="公司公告 ">
<StockNoticeList :stock-code="stockCode" />
</n-tab-pane>
<n-tab-pane name="行业研究" tab="行业研究 ">
<IndustryResearchReportList/>
</n-tab-pane>
<n-tab-pane name="当前热门" tab="当前热门">
<n-tabs type="card" animated>
<n-tab-pane name="全球" tab="全球">
<HotStockList :market-type="'10'"/>
</n-tab-pane>
<n-tab-pane name="沪深" tab="沪深">
<HotStockList :market-type="'12'"/>
</n-tab-pane>
<n-tab-pane name="港股" tab="港股">
<HotStockList :market-type="'13'"/>
</n-tab-pane>
<n-tab-pane name="美股" tab="美股">
<HotStockList :market-type="'11'"/>
</n-tab-pane>
<n-tab-pane name="热门话题" tab="热门话题">
<n-grid :cols="1" :y-gap="10">
<n-grid-item>
<HotTopics/>
</n-grid-item>
<!-- <n-grid-item>-->
<!-- <HotEvents/>-->
<!-- </n-grid-item>-->
</n-grid>
</n-tab-pane>
<n-tab-pane name="重大事件时间轴" tab="重大事件时间轴">
<InvestCalendarTimeLine />
</n-tab-pane>
<n-tab-pane name="财经日历" tab="财经日历">
<ClsCalendarTimeLine />
</n-tab-pane>
</n-tabs>
</n-tab-pane>
<n-tab-pane name="指标选股" tab="指标选股">
<select-stock />
</n-tab-pane>
</n-tabs> </n-tabs>
</n-card> </n-card>
<n-modal transform-origin="center" v-model:show="summaryModal" preset="card" style="width: 800px;" <n-modal transform-origin="center" v-model:show="summaryModal" preset="card" style="width: 800px;" :title="'AI市场资讯总结'" >
:title="'AI市场资讯总结'">
<n-spin size="small" :show="loading"> <n-spin size="small" :show="loading">
<MdPreview style="height: 440px;text-align: left" :modelValue="aiSummary" :theme="theme"/> <MdPreview style="height: 440px;text-align: left" :modelValue="aiSummary" :theme="theme"/>
</n-spin> </n-spin>
@ -616,10 +345,8 @@ function ReFlesh(source) {
</template> </template>
<template #action> <template #action>
<n-flex justify="space-between" style="margin-bottom: 10px"> <n-flex justify="space-between" style="margin-bottom: 10px">
<n-select style="width: 49%" v-model:value="sysPromptId" label-field="name" value-field="ID" <n-select style="width: 49%" v-model:value="sysPromptId" label-field="name" value-field="ID" :options="sysPromptOptions" placeholder="请选择系统提示词" />
:options="sysPromptOptions" placeholder="请选择系统提示词"/> <n-select style="width: 49%" v-model:value="question" label-field="name" value-field="content" :options="userPromptOptions" placeholder="请选择用户提示词" />
<n-select style="width: 49%" v-model:value="question" label-field="name" value-field="content"
:options="userPromptOptions" placeholder="请选择用户提示词"/>
</n-flex> </n-flex>
<n-flex justify="right"> <n-flex justify="right">
<n-input v-model:value="question" style="text-align: left" clearable <n-input v-model:value="question" style="text-align: left" clearable
@ -647,8 +374,6 @@ function ReFlesh(source) {
</n-input-group> </n-input-group>
</div> </div>
</template> </template>
<style scoped> <style scoped>

View File

@ -1,374 +0,0 @@
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {GetStockMoneyTrendByDay} from "../../wailsjs/go/main/App";
import * as echarts from "echarts";
const {code, name, darkTheme, days, chartHeight} = defineProps({
code: {
type: String,
default: ''
},
name: {
type: String,
default: ''
},
days: {
type: Number,
default: 14
},
chartHeight: {
type: Number,
default: 500
},
darkTheme: {
type: Boolean,
default: false
}
})
const LineChartRef = ref(null);
onMounted(
() => {
handleLine(code, days)
}
)
const handleLine = (code, days) => {
GetStockMoneyTrendByDay(code, days).then(result => {
//console.log("GetStockMoneyTrendByDay", result)
const chart = echarts.init(LineChartRef.value);
const categoryData = [];
const netamount_values = [];
const r0_net_values = [];
const trades_values = [];
let volume = []
let min = 0
let max = 0
for (let i = 0; i < result.length; i++) {
let resultElement = result[i]
categoryData.push(resultElement.opendate)
let netamount = (resultElement.netamount / 10000).toFixed(2);
netamount_values.push(netamount)
let price = Number(resultElement.trade);
trades_values.push(price)
r0_net_values.push((resultElement.r0_net / 10000).toFixed(2))
if (min === 0 || min > price) {
min = price
}
if (max < price) {
max = price
}
if (i > 0) {
let b = Number(Number(result[i].netamount) + Number(result[i - 1].netamount)) / 10000
volume.push(b.toFixed(2))
} else {
volume.push((Number(result[i].netamount) / 10000).toFixed(2))
}
}
//console.log("volume", volume)
const upColor = '#ec0000';
const downColor = '#00da3c';
let option = {
title: {
text: name,
left: '20px',
textStyle: {
color: darkTheme?'#ccc':'#456'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
lineStyle: {
color: '#376df4',
width: 1,
opacity: 1
}
},
borderWidth: 2,
borderColor: darkTheme?'#456':'#ccc',
backgroundColor: darkTheme?'#456':'#fff',
padding: 10,
textStyle: {
color: darkTheme?'#ccc':'#456'
},
},
axisPointer: {
link: [
{
xAxisIndex: 'all'
}
],
label: {
backgroundColor: '#888'
}
},
legend: {
show: true,
data: ['当日净流入', '主力当日净流入','累计净流入', '股价'],
selected: {
'当日净流入': true,
'主力当日净流入': true,
'累计净流入': true,
'股价': true,
},
//orient: 'vertical',
textStyle: {
color: darkTheme ? 'rgb(253,252,252)' : '#456'
},
right: 150,
},
dataZoom: [
{
type: 'inside',
xAxisIndex: [0, 1],
start: 86,
end: 100
},
{
show: true,
xAxisIndex: [0, 1],
type: 'slider',
top: '90%',
start: 86,
end: 100
}
],
grid: [
{
left: '8%',
right: '8%',
height: '50%',
},
{
left: '8%',
right: '8%',
top: '74%',
height: '15%'
},
],
xAxis: [
{
type: 'category',
data: categoryData,
axisPointer: {
z: 100
},
boundaryGap: false,
axisLine: { onZero: false },
splitLine: { show: false },
min: 'dataMin',
max: 'dataMax',
},
{
gridIndex: 1,
type: 'category',
data: categoryData,
axisLabel: {
show: false
},
}
],
yAxis: [
{
name: '当日净流入/万',
type: 'value',
axisLine: {
show: true
},
splitLine: {
show: false
},
},
{
name: '股价',
type: 'value',
min: min - 1,
max: max + 1,
minInterval: 0.01,
axisLine: {
show: true
},
splitLine: {
show: false
},
},
{
gridIndex: 1,
name: '累计净流入/万',
type: 'value',
axisLine: {
show: true
},
splitLine: {
show: false
},
},
],
series: [
{
yAxisIndex: 0,
name: '当日净流入',
data: netamount_values,
smooth: false,
showSymbol: false,
lineStyle: {
width: 2
},
markPoint: {
symbol: 'arrow',
symbolRotate: 90,
symbolSize: [10, 20],
symbolOffset: [10, 0],
itemStyle: {
color: '#0d7dfc'
},
label: {
position: 'right',
},
data: [
{type: 'max', name: 'Max'},
{type: 'min', name: 'Min'}
]
},
markLine: {
data: [
{
type: 'average',
name: 'Average',
lineStyle: {
color: '#0077ff',
width: 0.5
},
},
]
},
type: 'line'
},
{
yAxisIndex: 0,
name: '主力当日净流入',
data: r0_net_values,
smooth: false,
showSymbol: false,
lineStyle: {
width: 2
},
// markPoint: {
// symbol: 'arrow',
// symbolRotate: 90,
// symbolSize: [10, 20],
// symbolOffset: [10, 0],
// itemStyle: {
// color: '#0d7dfc'
// },
// label: {
// position: 'right',
// },
// data: [
// {type: 'max', name: 'Max'},
// {type: 'min', name: 'Min'}
// ]
// },
// markLine: {
// data: [
// {
// type: 'average',
// name: 'Average',
// lineStyle: {
// color: '#0077ff',
// width: 0.5
// },
// },
// ]
// },
type: 'bar'
},
{
yAxisIndex: 1,
name: '股价',
type: 'line',
data: trades_values,
smooth: true,
showSymbol: false,
lineStyle: {
width: 3
},
markPoint: {
symbol: 'arrow',
symbolRotate: 90,
symbolSize: [10, 20],
symbolOffset: [10, 0],
itemStyle: {
color: '#f39509'
},
label: {
position: 'right',
},
data: [
{type: 'max', name: 'Max'},
{type: 'min', name: 'Min'}
]
},
markLine: {
data: [
{
type: 'average',
name: 'Average',
lineStyle: {
color: '#f39509',
width: 0.5
},
},
]
},
},
{
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 2,
name: '累计净流入',
data: volume,
smooth: true,
showSymbol: false,
lineStyle: {
width: 2
},
markPoint: {
symbol: 'arrow',
symbolRotate: 90,
symbolSize: [10, 20],
symbolOffset: [10, 0],
// itemStyle: {
// color: '#f39509'
// },
label: {
position: 'right',
},
data: [
{type: 'max', name: 'Max'},
{type: 'min', name: 'Min'}
]
},
},
]
};
chart.setOption(option);
})
}
</script>
<template>
<div ref="LineChartRef" style="width: 100%;height: auto;" :style="{height:chartHeight+'px'}"></div>
</template>
<style scoped>
</style>

View File

@ -1,7 +1,4 @@
<script setup> <script setup>
import {ReFleshTelegraphList} from "../../wailsjs/go/main/App";
import {RefreshCircle, RefreshCircleSharp, RefreshOutline} from "@vicons/ionicons5";
const { headerTitle,newsList } = defineProps({ const { headerTitle,newsList } = defineProps({
headerTitle: { headerTitle: {
type: String, type: String,
@ -10,23 +7,14 @@ const { headerTitle,newsList } = defineProps({
newsList: { newsList: {
type: Array, type: Array,
default: () => [] default: () => []
},
})
const emits = defineEmits(['update:message'])
const updateMessage = () => {
emits('update:message', headerTitle)
} }
})
</script> </script>
<template> <template>
<n-list bordered> <n-list bordered>
<template #header> <template #header>
<n-flex justify="space-between"> {{ headerTitle }}
<n-tag :bordered="false" size="large" type="success" >{{ headerTitle }}</n-tag>
<n-button :bordered="false" @click="updateMessage"><n-icon color="#409EFF" size="25" :component="RefreshCircleSharp"/></n-button>
</n-flex>
</template> </template>
<n-list-item v-for="item in newsList"> <n-list-item v-for="item in newsList">
<n-space justify="start"> <n-space justify="start">

View File

@ -1,101 +0,0 @@
<script setup>
import {CaretDown, CaretUp, RefreshCircleOutline} from "@vicons/ionicons5";
import {NText,useMessage} from "naive-ui";
import {onBeforeUnmount, onMounted, onUnmounted, ref} from "vue";
import {GetMoneyRankSina} from "../../wailsjs/go/main/App";
import KLineChart from "./KLineChart.vue";
const props = defineProps({
headerTitle: {
type: String,
default: '净流入额排名'
},
sort: {
type: String,
default: 'netamount'
},
})
const message = useMessage()
const dataList= ref([])
const sort = ref(props.sort)
const interval = ref(null)
onMounted(()=>{
sort.value=props.sort
GetMoneyRankSinaData()
interval.value=setInterval(()=>{
GetMoneyRankSinaData()
},1000*60)
})
onBeforeUnmount(()=>{
clearInterval(interval.value)
})
function GetMoneyRankSinaData(){
message.loading("正在刷新数据...")
GetMoneyRankSina(sort.value).then(result => {
if(result.length>0){
dataList.value = result
}
})
}
</script>
<template>
<n-table striped size="small">
<n-thead>
<n-tr>
<n-th>代码</n-th>
<n-th>名称</n-th>
<n-th>最新价</n-th>
<n-th>涨跌幅</n-th>
<n-th>换手率</n-th>
<n-th>成交额/</n-th>
<n-th>流出资金/</n-th>
<n-th>流入资金/</n-th>
<n-th>净流入/</n-th>
<n-th>净流入率</n-th>
<n-th v-if="sort === 'r0_net'||sort==='r0_out'">主力流出/</n-th>
<n-th v-if="sort === 'r0_net'">主力流入/</n-th>
<n-th v-if="sort === 'r0_net'">主力净流入/</n-th>
<n-th >主力净流入率</n-th>
<n-th v-if="sort === 'r3_net'||sort==='r3_out'">散户流出/</n-th>
<n-th v-if="sort === 'r3_net'">散户流入/</n-th>
<n-th v-if="sort === 'r3_net'">散户净流入/</n-th>
<n-th >散户净流入率</n-th>
</n-tr>
</n-thead>
<n-tbody>
<n-tr v-for="item in dataList" :key="item.symbol">
<n-td><n-tag :bordered=false type="info">{{ item.symbol }}</n-tag></n-td>
<n-td>
<n-popover trigger="hover" placement="right">
<template #trigger>
<n-button tag="a" text :type="item.changeratio>0?'error':'success'" :bordered=false >{{ item.name }}</n-button>
</template>
<k-line-chart style="width: 800px" :code="item.symbol" :chart-height="500" :name="item.name" :k-days="20" :dark-theme="true"></k-line-chart>
</n-popover>
</n-td>
<n-td><n-text :type="item.changeratio>0?'error':'success'">{{item.trade}}</n-text></n-td>
<n-td><n-text :type="item.changeratio>0?'error':'success'">{{(item.changeratio*100).toFixed(2)}}%</n-text></n-td>
<n-td><n-text :type="item.turnover>500?'error':'info'">{{(item.turnover/100).toFixed(2)}}%</n-text></n-td>
<n-td><n-text type="info">{{(item.amount/10000).toFixed(2)}}</n-text></n-td>
<n-td><n-text type="info"> {{(item.outamount/10000).toFixed(2)}}</n-text></n-td>
<n-td><n-text type="info"> {{(item.inamount/10000).toFixed(2)}}</n-text></n-td>
<n-td><n-text type="info"> {{(item.netamount/10000).toFixed(2)}}</n-text></n-td>
<n-td><n-text :type="item.ratioamount>0?'error':'success'"> {{(item.ratioamount*100).toFixed(2)}}%</n-text></n-td>
<n-td v-if="sort === 'r0_net'||sort==='r0_out'"><n-text type="success"> {{(item.r0_out/10000).toFixed(2)}}</n-text></n-td>
<n-td v-if="sort === 'r0_net'"><n-text type="error"> {{(item.r0_in/10000).toFixed(2)}}</n-text></n-td>
<n-td v-if="sort === 'r0_net'"><n-text :type="item.r0_net>0?'error':'success'"> {{(item.r0_net/10000).toFixed(2)}}</n-text></n-td>
<n-td ><n-text :type="item.r0_ratio>0?'error':'success'"> {{(item.r0_ratio*100).toFixed(2)}}%</n-text></n-td>
<n-td v-if="sort === 'r3_net'||sort==='r3_out'"><n-text type="success"> {{(item.r3_out/10000).toFixed(2)}}</n-text></n-td>
<n-td v-if="sort === 'r3_net'"><n-text type="error"> {{(item.r3_in/10000).toFixed(2)}}</n-text></n-td>
<n-td v-if="sort === 'r3_net'"><n-text :type="item.r3_net>0?'error':'success'"> {{(item.r3_net/10000).toFixed(2)}}</n-text></n-td>
<n-td ><n-text :type="item.r3_ratio>0?'error':'success'"> {{(item.r3_ratio*100).toFixed(2)}}%</n-text></n-td>
</n-tr>
</n-tbody>
</n-table>
</template>
<style scoped>
</style>

View File

@ -45,7 +45,6 @@ const formValue = ref({
enableNews:false, enableNews:false,
darkTheme:true, darkTheme:true,
enableFund:false, enableFund:false,
enablePushNews:false,
}) })
const promptTemplates=ref([]) const promptTemplates=ref([])
onMounted(()=>{ onMounted(()=>{
@ -79,14 +78,13 @@ onMounted(()=>{
formValue.value.enableNews = res.enableNews formValue.value.enableNews = res.enableNews
formValue.value.darkTheme = res.darkTheme formValue.value.darkTheme = res.darkTheme
formValue.value.enableFund = res.enableFund formValue.value.enableFund = res.enableFund
formValue.value.enablePushNews = res.enablePushNews
//console.log(res) console.log(res)
}) })
//message.info("") //message.info("")
GetPromptTemplates("","").then(res=>{ GetPromptTemplates("","").then(res=>{
//console.log(res) console.log(res)
promptTemplates.value=res promptTemplates.value=res
}) })
}) })
@ -120,7 +118,6 @@ function saveConfig(){
enableNews:formValue.value.enableNews, enableNews:formValue.value.enableNews,
darkTheme:formValue.value.darkTheme, darkTheme:formValue.value.darkTheme,
enableFund:formValue.value.enableFund, enableFund:formValue.value.enableFund,
enablePushNews:formValue.value.enablePushNews
}) })
@ -168,7 +165,7 @@ function importConfig(){
let reader = new FileReader(); let reader = new FileReader();
reader.onload = (e) => { reader.onload = (e) => {
let config = JSON.parse(e.target.result); let config = JSON.parse(e.target.result);
//console.log(config) console.log(config)
formValue.value.ID = config.ID formValue.value.ID = config.ID
formValue.value.tushareToken = config.tushareToken formValue.value.tushareToken = config.tushareToken
formValue.value.dingPush = { formValue.value.dingPush = {
@ -198,7 +195,6 @@ function importConfig(){
formValue.value.enableNews = config.enableNews formValue.value.enableNews = config.enableNews
formValue.value.darkTheme = config.darkTheme formValue.value.darkTheme = config.darkTheme
formValue.value.enableFund = config.enableFund formValue.value.enableFund = config.enableFund
formValue.value.enablePushNews = config.enablePushNews
// formRef.value.resetFields() // formRef.value.resetFields()
}; };
reader.readAsText(file); reader.readAsText(file);
@ -208,7 +204,7 @@ function importConfig(){
window.onerror = function (event, source, lineno, colno, error) { window.onerror = function (event, source, lineno, colno, error) {
//console.log(event, source, lineno, colno, error) console.log(event, source, lineno, colno, error)
// //
EventsEmit("frontendError", { EventsEmit("frontendError", {
page: "settings.vue", page: "settings.vue",
@ -241,14 +237,14 @@ function savePrompt(){
AddPrompt(formPrompt.value).then(res=>{ AddPrompt(formPrompt.value).then(res=>{
message.success(res) message.success(res)
GetPromptTemplates("","").then(res=>{ GetPromptTemplates("","").then(res=>{
//console.log(res) console.log(res)
promptTemplates.value=res promptTemplates.value=res
}) })
showManagePromptsModal.value=false showManagePromptsModal.value=false
}) })
} }
function editPrompt(prompt){ function editPrompt(prompt){
//console.log(prompt) console.log(prompt)
formPrompt.value.ID=prompt.ID formPrompt.value.ID=prompt.ID
formPrompt.value.Name=prompt.name formPrompt.value.Name=prompt.name
formPrompt.value.Content=prompt.content formPrompt.value.Content=prompt.content
@ -259,7 +255,7 @@ function deletePrompt(ID){
DelPrompt(ID).then(res=>{ DelPrompt(ID).then(res=>{
message.success(res) message.success(res)
GetPromptTemplates("","").then(res=>{ GetPromptTemplates("","").then(res=>{
//console.log(res) console.log(res)
promptTemplates.value=res promptTemplates.value=res
}) })
}) })
@ -292,7 +288,7 @@ function deletePrompt(ID){
<n-form-item-gi :span="10" label="浏览器安装路径:" path="browserPath" > <n-form-item-gi :span="10" label="浏览器安装路径:" path="browserPath" >
<n-input type="text" placeholder="浏览器安装路径" v-model:value="formValue.browserPath" clearable /> <n-input type="text" placeholder="浏览器安装路径" v-model:value="formValue.browserPath" clearable />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi :span="6" label="指数基金:" path="enableFund" > <n-form-item-gi :span="6" label="是否启用指数基金:" path="enableFund" >
<n-switch v-model:value="formValue.enableFund" /> <n-switch v-model:value="formValue.enableFund" />
</n-form-item-gi> </n-form-item-gi>
</n-grid> </n-grid>
@ -301,21 +297,18 @@ function deletePrompt(ID){
<n-gi :span="24"> <n-gi :span="24">
<n-text type="success" style="font-size: 25px;font-weight: bold">通知设置</n-text> <n-text type="success" style="font-size: 25px;font-weight: bold">通知设置</n-text>
</n-gi> </n-gi>
<n-form-item-gi :span="4" label="钉钉推送:" path="dingPush.enable" > <n-form-item-gi :span="6" label="是否启用钉钉推送:" path="dingPush.enable" >
<n-switch v-model:value="formValue.dingPush.enable" /> <n-switch v-model:value="formValue.dingPush.enable" />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi :span="4" label="本地推送:" path="localPush.enable" > <n-form-item-gi :span="6" label="是否启用本地推送:" path="localPush.enable" >
<n-switch v-model:value="formValue.localPush.enable" /> <n-switch v-model:value="formValue.localPush.enable" />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi :span="4" label="弹幕功能:" path="enableDanmu" > <n-form-item-gi :span="5" label="弹幕功能:" path="enableDanmu" >
<n-switch v-model:value="formValue.enableDanmu" /> <n-switch v-model:value="formValue.enableDanmu" />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi :span="4" label="显示滚动快讯" path="enableNews" > <n-form-item-gi :span="5" label="是否显示滚动快讯(重启生效)" path="enableNews" >
<n-switch v-model:value="formValue.enableNews" /> <n-switch v-model:value="formValue.enableNews" />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi :span="4" label="市场资讯提醒:" path="enablePushNews" >
<n-switch v-model:value="formValue.enablePushNews" />
</n-form-item-gi>
<n-form-item-gi :span="22" v-if="formValue.dingPush.enable" label="钉钉机器人接口地址:" path="dingPush.dingRobot" > <n-form-item-gi :span="22" v-if="formValue.dingPush.enable" label="钉钉机器人接口地址:" path="dingPush.dingRobot" >
<n-input placeholder="请输入钉钉机器人接口地址" v-model:value="formValue.dingPush.dingRobot"/> <n-input placeholder="请输入钉钉机器人接口地址" v-model:value="formValue.dingPush.dingRobot"/>
<n-button type="primary" @click="sendTestNotice">发送测试通知</n-button> <n-button type="primary" @click="sendTestNotice">发送测试通知</n-button>
@ -326,7 +319,7 @@ function deletePrompt(ID){
<n-gi :span="24"> <n-gi :span="24">
<n-text type="success" style="font-size: 25px;font-weight: bold">OpenAI设置</n-text> <n-text type="success" style="font-size: 25px;font-weight: bold">OpenAI设置</n-text>
</n-gi> </n-gi>
<n-form-item-gi :span="3" label="AI诊股" path="openAI.enable" > <n-form-item-gi :span="3" label="是否启用AI诊股" path="openAI.enable" >
<n-switch v-model:value="formValue.openAI.enable" /> <n-switch v-model:value="formValue.openAI.enable" />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi :span="9" v-if="formValue.openAI.enable" label="openAI 接口地址:" path="openAI.baseUrl" > <n-form-item-gi :span="9" v-if="formValue.openAI.enable" label="openAI 接口地址:" path="openAI.baseUrl" >

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import {createMemoryHistory, createRouter, createWebHashHistory, createWebHistory} from 'vue-router' import {createMemoryHistory, createRouter, createWebHashHistory} from 'vue-router'
import stockView from '../components/stock.vue' import stockView from '../components/stock.vue'
import settingsView from '../components/settings.vue' import settingsView from '../components/settings.vue'
@ -12,11 +12,10 @@ const routes = [
{ path: '/settings', component: settingsView,name: 'settings' }, { path: '/settings', component: settingsView,name: 'settings' },
{ path: '/about', component: about,name: 'about' }, { path: '/about', component: about,name: 'about' },
{ path: '/market', component: market,name: 'market' }, { path: '/market', component: market,name: 'market' },
] ]
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHashHistory(),
routes, routes,
}) })

View File

@ -11,16 +11,10 @@ export function AddPrompt(arg1:models.Prompt):Promise<string>;
export function AddStockGroup(arg1:number,arg2:string):Promise<string>; export function AddStockGroup(arg1:number,arg2:string):Promise<string>;
export function AnalyzeSentiment(arg1:string):Promise<data.SentimentResult>;
export function CheckUpdate():Promise<void>; export function CheckUpdate():Promise<void>;
export function ClsCalendar():Promise<Array<any>>;
export function DelPrompt(arg1:number):Promise<string>; export function DelPrompt(arg1:number):Promise<string>;
export function EMDictCode(arg1:string):Promise<Array<any>>;
export function ExportConfig():Promise<string>; export function ExportConfig():Promise<string>;
export function Follow(arg1:string):Promise<string>; export function Follow(arg1:string):Promise<string>;
@ -39,12 +33,6 @@ export function GetGroupList():Promise<Array<data.Group>>;
export function GetGroupStockList(arg1:number):Promise<Array<data.GroupStock>>; export function GetGroupStockList(arg1:number):Promise<Array<data.GroupStock>>;
export function GetIndustryMoneyRankSina(arg1:string,arg2:string):Promise<Array<Record<string, any>>>;
export function GetIndustryRank(arg1:string,arg2:number):Promise<Array<any>>;
export function GetMoneyRankSina(arg1:string):Promise<Array<Record<string, any>>>;
export function GetPromptTemplates(arg1:string,arg2:string):Promise<any>; export function GetPromptTemplates(arg1:string,arg2:string):Promise<any>;
export function GetStockCommonKLine(arg1:string,arg2:string,arg3:number):Promise<any>; export function GetStockCommonKLine(arg1:string,arg2:string,arg3:number):Promise<any>;
@ -53,10 +41,6 @@ export function GetStockKLine(arg1:string,arg2:string,arg3:number):Promise<any>;
export function GetStockList(arg1:string):Promise<Array<data.StockBasic>>; export function GetStockList(arg1:string):Promise<Array<data.StockBasic>>;
export function GetStockMinutePriceLineData(arg1:string,arg2:string):Promise<Record<string, any>>;
export function GetStockMoneyTrendByDay(arg1:string,arg2:number):Promise<Array<Record<string, any>>>;
export function GetTelegraphList(arg1:string):Promise<any>; export function GetTelegraphList(arg1:string):Promise<any>;
export function GetVersionInfo():Promise<models.VersionInfo>; export function GetVersionInfo():Promise<models.VersionInfo>;
@ -67,24 +51,8 @@ export function GlobalStockIndexes():Promise<Record<string, any>>;
export function Greet(arg1:string):Promise<data.StockInfo>; export function Greet(arg1:string):Promise<data.StockInfo>;
export function HotEvent(arg1:number):Promise<any>;
export function HotStock(arg1:string):Promise<any>;
export function HotTopic(arg1:number):Promise<Array<any>>;
export function IndustryResearchReport(arg1:string):Promise<Array<any>>;
export function InvestCalendarTimeLine(arg1:string):Promise<Array<any>>;
export function LongTigerRank(arg1:string):Promise<any>;
export function NewChatStream(arg1:string,arg2:string,arg3:string,arg4:any):Promise<void>; export function NewChatStream(arg1:string,arg2:string,arg3:string,arg4:any):Promise<void>;
export function NewsPush(arg1:any):Promise<void>;
export function ReFleshTelegraphList(arg1:string):Promise<any>;
export function RemoveGroup(arg1:number):Promise<string>; export function RemoveGroup(arg1:number):Promise<string>;
export function RemoveStockGroup(arg1:string,arg2:string,arg3:number):Promise<string>; export function RemoveStockGroup(arg1:string,arg2:string,arg3:number):Promise<string>;
@ -93,8 +61,6 @@ export function SaveAIResponseResult(arg1:string,arg2:string,arg3:string,arg4:st
export function SaveAsMarkdown(arg1:string,arg2:string):Promise<string>; export function SaveAsMarkdown(arg1:string,arg2:string):Promise<string>;
export function SearchStock(arg1:string):Promise<Record<string, any>>;
export function SendDingDingMessage(arg1:string,arg2:string):Promise<string>; export function SendDingDingMessage(arg1:string,arg2:string):Promise<string>;
export function SendDingDingMessageByType(arg1:string,arg2:string,arg3:number):Promise<string>; export function SendDingDingMessageByType(arg1:string,arg2:string,arg3:number):Promise<string>;
@ -109,10 +75,6 @@ export function SetStockSort(arg1:number,arg2:string):Promise<void>;
export function ShareAnalysis(arg1:string,arg2:string):Promise<string>; export function ShareAnalysis(arg1:string,arg2:string):Promise<string>;
export function StockNotice(arg1:string):Promise<Array<any>>;
export function StockResearchReport(arg1:string):Promise<Array<any>>;
export function SummaryStockNews(arg1:string,arg2:any):Promise<void>; export function SummaryStockNews(arg1:string,arg2:any):Promise<void>;
export function UnFollow(arg1:string):Promise<string>; export function UnFollow(arg1:string):Promise<string>;

View File

@ -18,26 +18,14 @@ export function AddStockGroup(arg1, arg2) {
return window['go']['main']['App']['AddStockGroup'](arg1, arg2); return window['go']['main']['App']['AddStockGroup'](arg1, arg2);
} }
export function AnalyzeSentiment(arg1) {
return window['go']['main']['App']['AnalyzeSentiment'](arg1);
}
export function CheckUpdate() { export function CheckUpdate() {
return window['go']['main']['App']['CheckUpdate'](); return window['go']['main']['App']['CheckUpdate']();
} }
export function ClsCalendar() {
return window['go']['main']['App']['ClsCalendar']();
}
export function DelPrompt(arg1) { export function DelPrompt(arg1) {
return window['go']['main']['App']['DelPrompt'](arg1); return window['go']['main']['App']['DelPrompt'](arg1);
} }
export function EMDictCode(arg1) {
return window['go']['main']['App']['EMDictCode'](arg1);
}
export function ExportConfig() { export function ExportConfig() {
return window['go']['main']['App']['ExportConfig'](); return window['go']['main']['App']['ExportConfig']();
} }
@ -74,18 +62,6 @@ export function GetGroupStockList(arg1) {
return window['go']['main']['App']['GetGroupStockList'](arg1); return window['go']['main']['App']['GetGroupStockList'](arg1);
} }
export function GetIndustryMoneyRankSina(arg1, arg2) {
return window['go']['main']['App']['GetIndustryMoneyRankSina'](arg1, arg2);
}
export function GetIndustryRank(arg1, arg2) {
return window['go']['main']['App']['GetIndustryRank'](arg1, arg2);
}
export function GetMoneyRankSina(arg1) {
return window['go']['main']['App']['GetMoneyRankSina'](arg1);
}
export function GetPromptTemplates(arg1, arg2) { export function GetPromptTemplates(arg1, arg2) {
return window['go']['main']['App']['GetPromptTemplates'](arg1, arg2); return window['go']['main']['App']['GetPromptTemplates'](arg1, arg2);
} }
@ -102,14 +78,6 @@ export function GetStockList(arg1) {
return window['go']['main']['App']['GetStockList'](arg1); return window['go']['main']['App']['GetStockList'](arg1);
} }
export function GetStockMinutePriceLineData(arg1, arg2) {
return window['go']['main']['App']['GetStockMinutePriceLineData'](arg1, arg2);
}
export function GetStockMoneyTrendByDay(arg1, arg2) {
return window['go']['main']['App']['GetStockMoneyTrendByDay'](arg1, arg2);
}
export function GetTelegraphList(arg1) { export function GetTelegraphList(arg1) {
return window['go']['main']['App']['GetTelegraphList'](arg1); return window['go']['main']['App']['GetTelegraphList'](arg1);
} }
@ -130,42 +98,10 @@ export function Greet(arg1) {
return window['go']['main']['App']['Greet'](arg1); return window['go']['main']['App']['Greet'](arg1);
} }
export function HotEvent(arg1) {
return window['go']['main']['App']['HotEvent'](arg1);
}
export function HotStock(arg1) {
return window['go']['main']['App']['HotStock'](arg1);
}
export function HotTopic(arg1) {
return window['go']['main']['App']['HotTopic'](arg1);
}
export function IndustryResearchReport(arg1) {
return window['go']['main']['App']['IndustryResearchReport'](arg1);
}
export function InvestCalendarTimeLine(arg1) {
return window['go']['main']['App']['InvestCalendarTimeLine'](arg1);
}
export function LongTigerRank(arg1) {
return window['go']['main']['App']['LongTigerRank'](arg1);
}
export function NewChatStream(arg1, arg2, arg3, arg4) { export function NewChatStream(arg1, arg2, arg3, arg4) {
return window['go']['main']['App']['NewChatStream'](arg1, arg2, arg3, arg4); return window['go']['main']['App']['NewChatStream'](arg1, arg2, arg3, arg4);
} }
export function NewsPush(arg1) {
return window['go']['main']['App']['NewsPush'](arg1);
}
export function ReFleshTelegraphList(arg1) {
return window['go']['main']['App']['ReFleshTelegraphList'](arg1);
}
export function RemoveGroup(arg1) { export function RemoveGroup(arg1) {
return window['go']['main']['App']['RemoveGroup'](arg1); return window['go']['main']['App']['RemoveGroup'](arg1);
} }
@ -182,10 +118,6 @@ export function SaveAsMarkdown(arg1, arg2) {
return window['go']['main']['App']['SaveAsMarkdown'](arg1, arg2); return window['go']['main']['App']['SaveAsMarkdown'](arg1, arg2);
} }
export function SearchStock(arg1) {
return window['go']['main']['App']['SearchStock'](arg1);
}
export function SendDingDingMessage(arg1, arg2) { export function SendDingDingMessage(arg1, arg2) {
return window['go']['main']['App']['SendDingDingMessage'](arg1, arg2); return window['go']['main']['App']['SendDingDingMessage'](arg1, arg2);
} }
@ -214,14 +146,6 @@ export function ShareAnalysis(arg1, arg2) {
return window['go']['main']['App']['ShareAnalysis'](arg1, arg2); return window['go']['main']['App']['ShareAnalysis'](arg1, arg2);
} }
export function StockNotice(arg1) {
return window['go']['main']['App']['StockNotice'](arg1);
}
export function StockResearchReport(arg1) {
return window['go']['main']['App']['StockResearchReport'](arg1);
}
export function SummaryStockNews(arg1, arg2) { export function SummaryStockNews(arg1, arg2) {
return window['go']['main']['App']['SummaryStockNews'](arg1, arg2); return window['go']['main']['App']['SummaryStockNews'](arg1, arg2);
} }

View File

@ -290,26 +290,6 @@ export namespace data {
export class SentimentResult {
Score: number;
Category: number;
PositiveCount: number;
NegativeCount: number;
Description: string;
static createFrom(source: any = {}) {
return new SentimentResult(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Score = source["Score"];
this.Category = source["Category"];
this.PositiveCount = source["PositiveCount"];
this.NegativeCount = source["NegativeCount"];
this.Description = source["Description"];
}
}
export class Settings { export class Settings {
ID: number; ID: number;
// Go type: time // Go type: time
@ -342,7 +322,6 @@ export namespace data {
darkTheme: boolean; darkTheme: boolean;
browserPoolSize: number; browserPoolSize: number;
enableFund: boolean; enableFund: boolean;
enablePushNews: boolean;
static createFrom(source: any = {}) { static createFrom(source: any = {}) {
return new Settings(source); return new Settings(source);
@ -378,7 +357,6 @@ export namespace data {
this.darkTheme = source["darkTheme"]; this.darkTheme = source["darkTheme"];
this.browserPoolSize = source["browserPoolSize"]; this.browserPoolSize = source["browserPoolSize"];
this.enableFund = source["enableFund"]; this.enableFund = source["enableFund"];
this.enablePushNews = source["enablePushNews"];
} }
convertValues(a: any, classs: any, asMap: boolean = false): any { convertValues(a: any, classs: any, asMap: boolean = false): any {

17
go.mod
View File

@ -1,6 +1,8 @@
module go-stock module go-stock
go 1.23.0 go 1.23
toolchain go1.23.0
require ( require (
github.com/PuerkitoBio/goquery v1.10.1 github.com/PuerkitoBio/goquery v1.10.1
@ -9,18 +11,16 @@ require (
github.com/duke-git/lancet/v2 v2.3.4 github.com/duke-git/lancet/v2 v2.3.4
github.com/energye/systray v1.0.2 github.com/energye/systray v1.0.2
github.com/glebarez/sqlite v1.11.0 github.com/glebarez/sqlite v1.11.0
github.com/go-ego/gse v0.80.3
github.com/go-resty/resty/v2 v2.16.2 github.com/go-resty/resty/v2 v2.16.2
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
github.com/robertkrimen/otto v0.5.1 github.com/robertkrimen/otto v0.5.1
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/samber/lo v1.49.1 github.com/samber/lo v1.49.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/tidwall/gjson v1.14.2
github.com/wailsapp/wails/v2 v2.10.1 github.com/wailsapp/wails/v2 v2.10.1
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/sys v0.31.0 golang.org/x/sys v0.30.0
golang.org/x/text v0.23.0 golang.org/x/text v0.22.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/gorm v1.25.12 gorm.io/gorm v1.25.12
gorm.io/plugin/dbresolver v1.5.3 gorm.io/plugin/dbresolver v1.5.3
@ -62,18 +62,15 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c // indirect github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tkrajina/go-reflector v0.5.8 // indirect github.com/tkrajina/go-reflector v0.5.8 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vcaesar/cedar v0.20.2 // indirect
github.com/wailsapp/go-webview2 v1.0.19 // indirect github.com/wailsapp/go-webview2 v1.0.19 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect
go.uber.org/multierr v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect
golang.org/x/crypto v0.36.0 // indirect golang.org/x/crypto v0.33.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.38.0 // indirect golang.org/x/net v0.35.0 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.5 // indirect modernc.org/libc v1.22.5 // indirect

28
go.sum
View File

@ -26,8 +26,6 @@ github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9g
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-ego/gse v0.80.3 h1:YNFkjMhlhQnUeuoFcUEd1ivh6SOB764rT8GDsEbDiEg=
github.com/go-ego/gse v0.80.3/go.mod h1:Gt3A9Ry1Eso2Kza4MRaiZ7f2DTAvActmETY46Lxg0gU=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg= github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
@ -119,22 +117,12 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c h1:coVla7zpsycc+kA9NXpcvv2E4I7+ii6L5hZO2S6C3kw= github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c h1:coVla7zpsycc+kA9NXpcvv2E4I7+ii6L5hZO2S6C3kw=
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ= github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vcaesar/cedar v0.20.2 h1:TDx7AdZhilKcfE1WvdToTJf5VrC/FXcUOW+KY1upLZ4=
github.com/vcaesar/cedar v0.20.2/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik=
github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU=
github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU= github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU=
github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
@ -154,8 +142,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -173,8 +161,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -199,8 +187,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -220,8 +208,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

BIN
img.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 KiB

146
main.go
View File

@ -1,11 +1,10 @@
package main package main
import ( import (
"context"
"embed" "embed"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/duke-git/lancet/v2/slice" "github.com/duke-git/lancet/v2/convertor"
"github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger" "github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/menu"
@ -21,7 +20,6 @@ import (
"os" "os"
goruntime "runtime" goruntime "runtime"
"runtime/debug" "runtime/debug"
"strings"
"time" "time"
) )
@ -57,13 +55,43 @@ var VersionCommit string
func main() { func main() {
checkDir("data") checkDir("data")
db.Init("") db.Init("")
go AutoMigrate() db.Dao.AutoMigrate(&data.StockInfo{})
db.Dao.AutoMigrate(&data.StockBasic{})
db.Dao.AutoMigrate(&data.FollowedStock{})
db.Dao.AutoMigrate(&data.IndexBasic{})
db.Dao.AutoMigrate(&data.Settings{})
db.Dao.AutoMigrate(&models.AIResponseResult{})
db.Dao.AutoMigrate(&models.StockInfoHK{})
db.Dao.AutoMigrate(&models.StockInfoUS{})
db.Dao.AutoMigrate(&data.FollowedFund{})
db.Dao.AutoMigrate(&data.FundBasic{})
db.Dao.AutoMigrate(&models.PromptTemplate{})
db.Dao.AutoMigrate(&data.Group{})
db.Dao.AutoMigrate(&data.GroupStock{})
db.Dao.AutoMigrate(&models.Tags{})
db.Dao.AutoMigrate(&models.Telegraph{})
db.Dao.AutoMigrate(&models.TelegraphTags{})
//db.Dao.Model(&data.Group{}).Where("id = ?", 0).FirstOrCreate(&data.Group{ //db.Dao.Model(&data.Group{}).Where("id = ?", 0).FirstOrCreate(&data.Group{
// Name: "默认分组", // Name: "默认分组",
// Sort: 0, // Sort: 0,
//}) //})
if stocksBin != nil && len(stocksBin) > 0 {
go initStockData()
}
log.SugaredLogger.Infof("init stocksBinHK %d", len(stocksBinHK))
if stocksBinHK != nil && len(stocksBinHK) > 0 {
go initStockDataHK()
}
log.SugaredLogger.Infof("init stocksBinUS %d", len(stocksBinUS))
if stocksBinUS != nil && len(stocksBinUS) > 0 {
go initStockDataUS()
}
updateBasicInfo()
// Create an instance of the app structure // Create an instance of the app structure
app := NewApp() app := NewApp()
AppMenu := menu.NewMenu() AppMenu := menu.NewMenu()
@ -186,30 +214,7 @@ func main() {
} }
func AutoMigrate() { func initStockDataUS() {
db.Dao.AutoMigrate(&data.StockInfo{})
db.Dao.AutoMigrate(&data.StockBasic{})
db.Dao.AutoMigrate(&data.FollowedStock{})
db.Dao.AutoMigrate(&data.IndexBasic{})
db.Dao.AutoMigrate(&data.Settings{})
db.Dao.AutoMigrate(&models.AIResponseResult{})
db.Dao.AutoMigrate(&models.StockInfoHK{})
db.Dao.AutoMigrate(&models.StockInfoUS{})
db.Dao.AutoMigrate(&data.FollowedFund{})
db.Dao.AutoMigrate(&data.FundBasic{})
db.Dao.AutoMigrate(&models.PromptTemplate{})
db.Dao.AutoMigrate(&data.Group{})
db.Dao.AutoMigrate(&data.GroupStock{})
db.Dao.AutoMigrate(&models.Tags{})
db.Dao.AutoMigrate(&models.Telegraph{})
db.Dao.AutoMigrate(&models.TelegraphTags{})
db.Dao.AutoMigrate(&models.LongTigerRankData{})
}
func initStockDataUS(ctx context.Context) {
defer func() {
go runtime.EventsEmit(ctx, "loadingMsg", "done")
}()
var v []models.StockInfoUS var v []models.StockInfoUS
err := json.Unmarshal(stocksBinUS, &v) err := json.Unmarshal(stocksBinUS, &v)
if err != nil { if err != nil {
@ -217,9 +222,6 @@ func initStockDataUS(ctx context.Context) {
return return
} }
log.SugaredLogger.Infof("init stock data us %d", len(v)) log.SugaredLogger.Infof("init stock data us %d", len(v))
var total int64
db.Dao.Model(&models.StockInfoUS{}).Count(&total)
if total != int64(len(v)) {
for _, item := range v { for _, item := range v {
var count int64 var count int64
db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", item.Code).Count(&count) db.Dao.Model(&models.StockInfoUS{}).Where("code = ?", item.Code).Count(&count)
@ -230,12 +232,8 @@ func initStockDataUS(ctx context.Context) {
db.Dao.Model(&models.StockInfoUS{}).Create(&item) db.Dao.Model(&models.StockInfoUS{}).Create(&item)
} }
} }
}
func initStockDataHK(ctx context.Context) { func initStockDataHK() {
defer func() {
go runtime.EventsEmit(ctx, "loadingMsg", "done")
}()
var v []models.StockInfoHK var v []models.StockInfoHK
err := json.Unmarshal(stocksBinHK, &v) err := json.Unmarshal(stocksBinHK, &v)
if err != nil { if err != nil {
@ -243,9 +241,6 @@ func initStockDataHK(ctx context.Context) {
return return
} }
log.SugaredLogger.Infof("init stock data hk %d", len(v)) log.SugaredLogger.Infof("init stock data hk %d", len(v))
var total int64
db.Dao.Model(&models.StockInfoHK{}).Count(&total)
if total != int64(len(v)) {
for _, item := range v { for _, item := range v {
var count int64 var count int64
db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", item.Code).Count(&count) db.Dao.Model(&models.StockInfoHK{}).Where("code = ?", item.Code).Count(&count)
@ -257,8 +252,6 @@ func initStockDataHK(ctx context.Context) {
} }
} }
}
func updateBasicInfo() { func updateBasicInfo() {
config := data.NewSettingsApi(&data.Settings{}).GetConfig() config := data.NewSettingsApi(&data.Settings{}).GetConfig()
if config.UpdateBasicInfoOnStart { if config.UpdateBasicInfoOnStart {
@ -268,11 +261,7 @@ func updateBasicInfo() {
} }
} }
func initStockData(ctx context.Context) { func initStockData() {
defer func() {
go runtime.EventsEmit(ctx, "loadingMsg", "done")
}()
fields := "ts_code,symbol,name,area,industry,cnspell,market,list_date,act_name,act_ent_type,fullname,exchange,list_status,curr_type,enname,delist_date,is_hs"
log.SugaredLogger.Info("init stock data") log.SugaredLogger.Info("init stock data")
res := &data.TushareStockBasicResponse{} res := &data.TushareStockBasicResponse{}
err := json.Unmarshal(stocksBin, res) err := json.Unmarshal(stocksBin, res)
@ -280,24 +269,26 @@ func initStockData(ctx context.Context) {
log.SugaredLogger.Error(err.Error()) log.SugaredLogger.Error(err.Error())
return return
} }
for _, item := range res.Data.Items { for _, item := range res.Data.Items {
stock := &data.StockBasic{} stock := &data.StockBasic{}
stockData := map[string]any{} stock.Exchange = convertor.ToString(item[0])
for _, field := range strings.Split(fields, ",") { stock.IsHs = convertor.ToString(item[1])
//logger.SugaredLogger.Infof("field: %s", field) stock.Name = convertor.ToString(item[2])
idx := slice.IndexOf(res.Data.Fields, field) stock.Industry = convertor.ToString(item[3])
if idx == -1 { stock.ListStatus = convertor.ToString(item[4])
continue stock.ActName = convertor.ToString(item[5])
} stock.ID = uint(item[6].(float64))
stockData[field] = item[idx] stock.CurrType = convertor.ToString(item[7])
} stock.Area = convertor.ToString(item[8])
jsonData, _ := json.Marshal(stockData) stock.ListDate = convertor.ToString(item[9])
err := json.Unmarshal(jsonData, stock) stock.DelistDate = convertor.ToString(item[10])
if err != nil { stock.ActEntType = convertor.ToString(item[11])
continue stock.TsCode = convertor.ToString(item[12])
} stock.Symbol = convertor.ToString(item[13])
stock.ID = 0 stock.Cnspell = convertor.ToString(item[14])
stock.Fullname = convertor.ToString(item[20])
stock.Ename = convertor.ToString(item[21])
var count int64 var count int64
db.Dao.Model(&data.StockBasic{}).Where("ts_code = ?", stock.TsCode).Count(&count) db.Dao.Model(&data.StockBasic{}).Where("ts_code = ?", stock.TsCode).Count(&count)
if count > 0 { if count > 0 {
@ -305,38 +296,7 @@ func initStockData(ctx context.Context) {
} else { } else {
db.Dao.Create(stock) db.Dao.Create(stock)
} }
//db.Dao.Model(&data.StockBasic{}).FirstOrCreate(stock, &data.StockBasic{TsCode: stock.TsCode}).Where("ts_code = ?", stock.TsCode).Updates(stock)
} }
//for _, item := range res.Data.Items {
// stock := &data.StockBasic{}
// stock.Exchange = convertor.ToString(item[0])
// stock.IsHs = convertor.ToString(item[1])
// stock.Name = convertor.ToString(item[2])
// stock.Industry = convertor.ToString(item[3])
// stock.ListStatus = convertor.ToString(item[4])
// stock.ActName = convertor.ToString(item[5])
// stock.ID = uint(item[6].(float64))
// stock.CurrType = convertor.ToString(item[7])
// stock.Area = convertor.ToString(item[8])
// stock.ListDate = convertor.ToString(item[9])
// stock.DelistDate = convertor.ToString(item[10])
// stock.ActEntType = convertor.ToString(item[11])
// stock.TsCode = convertor.ToString(item[12])
// stock.Symbol = convertor.ToString(item[13])
// stock.Cnspell = convertor.ToString(item[14])
// stock.Fullname = convertor.ToString(item[20])
// stock.Ename = convertor.ToString(item[21])
//
// var count int64
// db.Dao.Model(&data.StockBasic{}).Where("ts_code = ?", stock.TsCode).Count(&count)
// if count > 0 {
// continue
// } else {
// db.Dao.Create(stock)
// }
//}
} }
func checkDir(dir string) { func checkDir(dir string) {