diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8bfb5de..dcdbe05 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -47,4 +47,4 @@ jobs: go-version: '1.24' build-tags: ${{ github.ref_name }} build-commit-message: ${{ steps.get_commit_message.outputs.commit_message }} - node-version: '18.x' + node-version: '20.x' diff --git a/README.md b/README.md index e8511ef..647c819 100644 --- a/README.md +++ b/README.md @@ -26,16 +26,17 @@ ### 💬 支持大模型/平台 -| 模型 | 状态 | 备注 | -| --- | --- |-----------------------------------------------------------------------------------------------------------------------------------------------------| -| [OpenAI](https://platform.openai.com/) | ✅ | 可接入任何 OpenAI 接口格式模型 | -| [Ollama](https://ollama.com/) | ✅ | 本地大模型运行平台 | -| [LMStudio](https://lmstudio.ai/) | ✅ | 本地大模型运行平台 | -| [AnythingLLM](https://anythingllm.com/) | ✅ | 本地知识库 | -| [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) | +| 模型 | 状态 | 备注 | +| --- | --- |---------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [OpenAI](https://platform.openai.com/) | ✅ | 可接入任何 OpenAI 接口格式模型 | +| [Ollama](https://ollama.com/) | ✅ | 本地大模型运行平台 | +| [LMStudio](https://lmstudio.ai/) | ✅ | 本地大模型运行平台 | +| [AnythingLLM](https://anythingllm.com/) | ✅ | 本地知识库 | +| [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) | ### 各位亲爱的朋友们,如果您对这个项目感兴趣,请先给我一个star吧,谢谢!💕 +- 优云智算(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) - 火山方舟:每个模型注册即送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) @@ -46,6 +47,8 @@ ## 🧩 重大功能开发计划 | 功能说明 | 状态 | 备注 | |-----------------|----|----------------------------------------------------------------------------------------------------------| +| 股票分析知识库 | 🚧 | 未来计划 | +| Ai智能选股 | 🚧 | Ai智能选股功能开发中(下半年重点开发计划) | | ETF支持 | 🚧 | ETF数据支持 (目前可以查看净值和估值) | | 美股支持 | ✅ | 美股数据支持 | | 港股支持 | ✅ | 港股数据支持 | @@ -54,7 +57,9 @@ | 不再强制依赖Chrome浏览器 | ✅ | 默认使用edge浏览器抓取新闻资讯 | ## 👀 更新日志 - +### 2025.06.30 添加指标选股功能 +### 2025.06.27 添加财经日历和重大事件时间轴功能 +### 2025.06.25 添加热门股票、事件和话题功能 ### 2025.06.18 更新内置股票基础数据,软件内实时市场资讯信息提醒,添加行业研究功能 ### 2025.06.15 添加公司公告信息搜索/查看功能 ### 2025.06.15 添加个股研报到弹出菜单 diff --git a/app_common.go b/app_common.go index a73a611..a82ed4b 100644 --- a/app_common.go +++ b/app_common.go @@ -31,3 +31,31 @@ func (a App) EMDictCode(code string) []any { 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() +} diff --git a/backend/data/market_news_api.go b/backend/data/market_news_api.go index d1e5938..904afb3 100644 --- a/backend/data/market_news_api.go +++ b/backend/data/market_news_api.go @@ -574,3 +574,130 @@ func (m MarketNewsApi) TradingViewNews() *[]models.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) +} diff --git a/backend/data/market_news_api_test.go b/backend/data/market_news_api_test.go index 01d489b..9f24c16 100644 --- a/backend/data/market_news_api_test.go +++ b/backend/data/market_news_api_test.go @@ -108,3 +108,46 @@ func TestTradingViewNews(t *testing.T) { 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) + } +} diff --git a/backend/data/search_stock_api.go b/backend/data/search_stock_api.go new file mode 100644 index 0000000..cea7d58 --- /dev/null +++ b/backend/data/search_stock_api.go @@ -0,0 +1,55 @@ +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 +} diff --git a/backend/data/search_stock_api_test.go b/backend/data/search_stock_api_test.go new file mode 100644 index 0000000..4ca5646 --- /dev/null +++ b/backend/data/search_stock_api_test.go @@ -0,0 +1,25 @@ +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) + //} + +} diff --git a/backend/data/stock_data_api.go b/backend/data/stock_data_api.go index 8b178e3..f88b590 100644 --- a/backend/data/stock_data_api.go +++ b/backend/data/stock_data_api.go @@ -400,7 +400,20 @@ func (receiver StockDataApi) Follow(stockCode string) string { logger.SugaredLogger.Error(err) 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) db.Dao.Model(&FollowedStock{}).Raw("select max(sort) as sort from followed_stock").Scan(&maxSort) @@ -463,15 +476,64 @@ func (receiver StockDataApi) SetAlarmChangePercent(val, alarmPrice float64, stoc return "设置成功" } -func (receiver StockDataApi) SetStockSort(sort int64, stockCode string) { - if strutil.HasPrefixAny(stockCode, []string{"gb_"}) { - stockCode = strings.ToLower(stockCode) - stockCode = strings.Replace(stockCode, "gb_", "us", 1) +func (receiver StockDataApi) SetStockSort(newSort int64, stockCode string) { + //if strutil.HasPrefixAny(stockCode, []string{"gb_"}) { + // stockCode = strings.ToLower(stockCode) + // stockCode = strings.Replace(stockCode, "gb_", "us", 1) + //} + + // 获取当前排序值 + var currentStock FollowedStock + if err := db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", strings.ToLower(stockCode)).First(¤tStock).Error; err != nil { + logger.SugaredLogger.Error("找不到当前股票: ", err.Error()) + return } - err := db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", strings.ToLower(stockCode)).Update("sort", sort).Error - if err != nil { - logger.SugaredLogger.Error(err.Error()) + + oldSort := currentStock.Sort + + // 如果排序值没有变化,直接返回 + 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) { if strutil.HasPrefixAny(stockCode, []string{"gb_"}) { diff --git a/backend/models/models.go b/backend/models/models.go index f513055..10d237b 100644 --- a/backend/models/models.go +++ b/backend/models/models.go @@ -317,3 +317,48 @@ type TVNews struct { 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"` +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f53e553..abf5ea7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,7 +11,7 @@ "@types/file-saver": "^2.0.7", "@vavt/cm-extension": "^1.8.0", "@vavt/v3-extension": "^3.0.0", - "@vicons/ionicons5": "^0.13.0", + "date-fns": "^4.1.0", "echarts": "^5.6.0", "file-saver": "^2.0.5", "html2canvas": "^1.4.1", @@ -22,6 +22,14 @@ "vue3-danmaku": "^1.6.1" }, "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", "html-docx-js-typescript": "^0.1.5", "naive-ui": "^2.41.0", @@ -1433,10 +1441,53 @@ "md-editor-v3": ">=5.2.0" } }, + "node_modules/@vicons/antd": { + "version": "0.13.0", + "resolved": "https://registry.npmmirror.com/@vicons/antd/-/antd-0.13.0.tgz", + "integrity": "sha512-yrUGoUSz2BbGupk9ghQOahc04n5H3MwUDM9pVPsLh9U1uqB47oRWZvYRiZaT1JKPqgTgSE6BXcVw4i9MOF4M+g==", + "dev": true + }, + "node_modules/@vicons/carbon": { + "version": "0.13.0", + "resolved": "https://registry.npmmirror.com/@vicons/carbon/-/carbon-0.13.0.tgz", + "integrity": "sha512-Z/jExyyS4gsXJc66oTqV/j98nsaiX2JlQ0IUwu9Ms3rztf8VOHEQRuX8Jey1/zbxJpFY/tU+bWvKPRFYGIvCWQ==", + "dev": true + }, + "node_modules/@vicons/fa": { + "version": "0.13.0", + "resolved": "https://registry.npmmirror.com/@vicons/fa/-/fa-0.13.0.tgz", + "integrity": "sha512-BFcDewcT78fSn4Y/fOgqlswbLUEW3+qJK2iJiNtgmkMzadBVpDXhNyVKsYM3V2uKPvDUrZT0JCWDWVRCiBXJZA==", + "dev": true + }, + "node_modules/@vicons/fluent": { + "version": "0.13.0", + "resolved": "https://registry.npmmirror.com/@vicons/fluent/-/fluent-0.13.0.tgz", + "integrity": "sha512-bYGZsOE3qzvm3Cm43e7tybgGlr5ZUpYqtRZq0g0Tfupe8jIzLolpvQLNUt1zS8Mgt6goTbUk5YH7Fkv16jkykg==", + "dev": true + }, + "node_modules/@vicons/ionicons4": { + "version": "0.13.0", + "resolved": "https://registry.npmmirror.com/@vicons/ionicons4/-/ionicons4-0.13.0.tgz", + "integrity": "sha512-5WHIl/4R5a4i9GONa+hIQWxg/WczrbsCdqxawHZvdd3drsEr+Q3yzlfS+NNRO4WS3uDW2uWLCwoW+yp5TgcKeQ==", + "dev": true + }, "node_modules/@vicons/ionicons5": { "version": "0.13.0", "resolved": "https://registry.npmmirror.com/@vicons/ionicons5/-/ionicons5-0.13.0.tgz", - "integrity": "sha512-zvZKBPjEXKN7AXNo2Na2uy+nvuv6SP4KAMQxpKL2vfHMj0fSvuw7JZcOPCjQC3e7ayssKnaoFVAhbYcW6v41qQ==" + "integrity": "sha512-zvZKBPjEXKN7AXNo2Na2uy+nvuv6SP4KAMQxpKL2vfHMj0fSvuw7JZcOPCjQC3e7ayssKnaoFVAhbYcW6v41qQ==", + "dev": true + }, + "node_modules/@vicons/material": { + "version": "0.13.0", + "resolved": "https://registry.npmmirror.com/@vicons/material/-/material-0.13.0.tgz", + "integrity": "sha512-lKVxFNprM+CaBkUH3gt6VjIeiMsKQl2zARQMwTCZruQl2vRHzyeZiKeCflWS99CEfv2JzX/6y697smxlzyxcVw==", + "dev": true + }, + "node_modules/@vicons/tabler": { + "version": "0.13.0", + "resolved": "https://registry.npmmirror.com/@vicons/tabler/-/tabler-0.13.0.tgz", + "integrity": "sha512-AykuhiqjszkIoAL/7knIFm6RDOBS1ZmQdJfQ+RNLEah0fVsxykUFCfMBSNZh8lOzC85EtdD1k5g/sv5GYk0Ohg==", + "dev": true }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.1", @@ -1667,10 +1718,10 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/date-fns": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-3.6.0.tgz", - "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", - "dev": true, + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" @@ -2016,6 +2067,17 @@ "vue": "^3.0.0" } }, + "node_modules/naive-ui/node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", diff --git a/frontend/package.json b/frontend/package.json index 64919e4..ebc6914 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,7 +12,7 @@ "@types/file-saver": "^2.0.7", "@vavt/cm-extension": "^1.8.0", "@vavt/v3-extension": "^3.0.0", - "@vicons/ionicons5": "^0.13.0", + "date-fns": "^4.1.0", "echarts": "^5.6.0", "file-saver": "^2.0.5", "html2canvas": "^1.4.1", @@ -23,6 +23,14 @@ "vue3-danmaku": "^1.6.1" }, "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", "html-docx-js-typescript": "^0.1.5", "naive-ui": "^2.41.0", diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 3f00715..fce852e 100644 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -b71b647d53bb771e87fac6e1372d9acf \ No newline at end of file +2d63c3a999d797889c01d6c96451b197 \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 930966c..f4a78ab 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -14,7 +14,7 @@ import {createDiscreteApi,darkTheme,lightTheme , NIcon, NText,dateZhCN,zhCN} fro import { AlarmOutline, AnalyticsOutline, - BarChartSharp, EaselSharp, + BarChartSharp, Bonfire, BonfireOutline, EaselSharp, ExpandOutline, Flag, Flame, FlameSharp, InformationOutline, LogoGithub, @@ -28,6 +28,11 @@ import { Wallet, WarningOutline, } from '@vicons/ionicons5' import {AnalyzeSentiment, GetConfig, GetGroupList} 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"; @@ -241,7 +246,7 @@ const menuOptions = ref([ {default: () => '龙虎榜',} ), key: 'market6', - icon: renderIcon(Skull), + icon: renderIcon(Dragon), }, { label: () => @@ -262,7 +267,7 @@ const menuOptions = ref([ {default: () => '个股研报',} ), key: 'market7', - icon: renderIcon(NewspaperSharp), + icon: renderIcon(StockOutlined), }, { label: () => @@ -283,7 +288,7 @@ const menuOptions = ref([ {default: () => '公司公告',} ), key: 'market8', - icon: renderIcon(NewspaperSharp), + icon: renderIcon(NotificationFilled), }, { label: () => @@ -304,7 +309,49 @@ const menuOptions = ref([ {default: () => '行业研究',} ), key: 'market9', - icon: renderIcon(NewspaperSharp), + 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), }, ] }, diff --git a/frontend/src/components/ClsCalendarTimeLine.vue b/frontend/src/components/ClsCalendarTimeLine.vue new file mode 100644 index 0000000..c823298 --- /dev/null +++ b/frontend/src/components/ClsCalendarTimeLine.vue @@ -0,0 +1,102 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/HotEvents.vue b/frontend/src/components/HotEvents.vue new file mode 100644 index 0000000..8571f3f --- /dev/null +++ b/frontend/src/components/HotEvents.vue @@ -0,0 +1,37 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/HotStockList.vue b/frontend/src/components/HotStockList.vue new file mode 100644 index 0000000..9846deb --- /dev/null +++ b/frontend/src/components/HotStockList.vue @@ -0,0 +1,88 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/HotTopics.vue b/frontend/src/components/HotTopics.vue new file mode 100644 index 0000000..e203788 --- /dev/null +++ b/frontend/src/components/HotTopics.vue @@ -0,0 +1,73 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/InvestCalendarTimeLine.vue b/frontend/src/components/InvestCalendarTimeLine.vue new file mode 100644 index 0000000..54d8b3d --- /dev/null +++ b/frontend/src/components/InvestCalendarTimeLine.vue @@ -0,0 +1,108 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/KLineChart.vue b/frontend/src/components/KLineChart.vue index 5791430..e1889c1 100644 --- a/frontend/src/components/KLineChart.vue +++ b/frontend/src/components/KLineChart.vue @@ -60,7 +60,7 @@ function handleKLine(code,name){ ////console.log("values",values) let option = { title: { - text: name, + text: name+" "+code, left: '20px', textStyle: { color: darkTheme?'#ccc':'#456' diff --git a/frontend/src/components/SelectStock.vue b/frontend/src/components/SelectStock.vue new file mode 100644 index 0000000..af17d2d --- /dev/null +++ b/frontend/src/components/SelectStock.vue @@ -0,0 +1,126 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/about.vue b/frontend/src/components/about.vue index 399760d..a40f660 100644 --- a/frontend/src/components/about.vue +++ b/frontend/src/components/about.vue @@ -135,6 +135,7 @@ EventsOn("updateVersion",async (msg) => {

感谢以下开发者: + 浓睡不消残酒 @gnim2600 @XXXiaohuayanGGG @2lovecode diff --git a/frontend/src/components/market.vue b/frontend/src/components/market.vue index 7944673..44eb20c 100644 --- a/frontend/src/components/market.vue +++ b/frontend/src/components/market.vue @@ -26,6 +26,12 @@ 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'); @@ -557,8 +563,41 @@ function ReFlesh(source) { - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { //console.log(`Export progress: ${progress.ratio * 100}%`); }; -const enableEditor= ref(false) +const enableEditor = ref(false) const mdPreviewRef = ref(null) -const mdEditorRef = ref(null) +const mdEditorRef = ref(null) const tipsRef = ref(null) const message = useMessage() const notify = useNotification() -const stocks=ref([]) -const results=ref({}) -const stockList=ref([]) -const followList=ref([]) -const groupList=ref([]) -const options=ref([]) +const stocks = ref([]) +const results = ref({}) +const stockList = ref([]) +const followList = ref([]) +const groupList = ref([]) +const options = ref([]) const modalShow = ref(false) const modalShow2 = ref(false) const modalShow3 = ref(false) @@ -107,85 +107,82 @@ const formModel = ref({ costPrice: 0.000, volume: 0, alarm: 0, - alarmPrice:0, - sort:999, - cron:"", + alarmPrice: 0, + sort: 999, + cron: "", }) -const promptTemplates=ref([]) -const sysPromptOptions=ref([]) -const userPromptOptions=ref([]) +const promptTemplates = ref([]) +const sysPromptOptions = ref([]) +const userPromptOptions = ref([]) const data = reactive({ - modelName:"", + modelName: "", chatId: "", - question:"", - sysPromptId:null, + question: "", + sysPromptId: null, name: "", code: "", - fenshiURL:"", - kURL:"", + fenshiURL: "", + kURL: "", resultText: "Please enter your name below 👇", fullscreen: false, airesult: "", openAiEnable: false, loading: true, enableDanmu: false, - darkTheme:false, - changePercent:0 + darkTheme: false, + changePercent: 0 }) -const feishiInterval= ref(null) +const feishiInterval = ref(null) -const currentGroupId=ref(0) +const currentGroupId = ref(0) - -const theme=computed(() => { +const theme = computed(() => { return data.darkTheme ? 'dark' : 'light' }) -const danmakuColor = computed(()=> { +const danmakuColor = computed(() => { return data.darkTheme ? 'color:#fff' : 'color:#000' }) const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png'); const sortedResults = computed(() => { - ////console.log("computed",sortedResults.value) - const sortedKeys =keys(results.value).sort(); - ////console.log("sortedKeys",sortedKeys) + const sortedKeys = keys(results.value).sort(); const sortedObject = {}; sortedKeys.forEach(key => { - sortedObject[key] = results.value[key]; + sortedObject[key] = results.value[key]; }); return sortedObject }); -const groupResults=computed(() => { - const group={} - for (const key in sortedResults.value) { - if(stocks.value.includes(sortedResults.value[key]['股票代码'])){ - group[key]=sortedResults.value[key] +const groupResults = computed(() => { + const group = {} + for (const key in sortedResults.value) { + if (stocks.value.includes(sortedResults.value[key]['股票代码'])) { + group[key] = sortedResults.value[key] } } return group }) -const showPopover=ref(false) +const showPopover = ref(false) -onBeforeMount(()=>{ +onBeforeMount(() => { GetGroupList().then(result => { - groupList.value=result - if(route.query.groupId){ - message.success("切换分组:"+route.query.groupName) - currentGroupId.value=Number(route.query.groupId) + groupList.value = result + if (route.query.groupId) { + message.success("切换分组:" + route.query.groupName) + currentGroupId.value = Number(route.query.groupId) //console.log("route.params",route.query) } }) GetStockList("").then(result => { stockList.value = result - options.value=result.map(item => { + options.value = result.map(item => { return { - label: item.name+" - "+item.ts_code, + label: item.name + " - " + item.ts_code, value: item.ts_code } }) @@ -201,11 +198,11 @@ onBeforeMount(()=>{ data.darkTheme = true } }) - GetPromptTemplates("","").then(res=>{ - promptTemplates.value=res + GetPromptTemplates("", "").then(res => { + promptTemplates.value = res - sysPromptOptions.value=promptTemplates.value.filter(item => item.type === '模型系统Prompt') - userPromptOptions.value=promptTemplates.value.filter(item => item.type === '模型用户Prompt') + sysPromptOptions.value = promptTemplates.value.filter(item => item.type === '模型系统Prompt') + userPromptOptions.value = promptTemplates.value.filter(item => item.type === '模型用户Prompt') //console.log("userPromptOptions",userPromptOptions.value) //console.log("sysPromptOptions",sysPromptOptions.value) @@ -215,24 +212,15 @@ onBeforeMount(()=>{ onMounted(() => { message.loading("Loading...") - // //console.log(`the component is now mounted.`) - - // ticker.value=setInterval(() => { - // if(isTradingTime()){ - // //monitor() - // //data.fenshiURL='http://image.sinajs.cn/newchart/min/n/'+data.code+'.gif'+"?t="+Date.now() - // } - // }, 3500) GetFollowList(currentGroupId.value).then(result => { - followList.value = result + followList.value = result for (const followedStock of result) { - if(followedStock.StockCode.startsWith("us")){ - followedStock.StockCode="gb_"+ followedStock.StockCode.replace("us", "").toLowerCase() + if (followedStock.StockCode.startsWith("us")) { + followedStock.StockCode = "gb_" + followedStock.StockCode.replace("us", "").toLowerCase() } if (!stocks.value.includes(followedStock.StockCode)) { - ////console.log("followList",followedStock.StockCode) stocks.value.push(followedStock.StockCode) } } @@ -253,7 +241,7 @@ onMounted(() => { }; ws.value.onmessage = (event) => { - if(data.enableDanmu){ + if (data.enableDanmu) { danmus.value.push(event.data); } }; @@ -268,7 +256,7 @@ onMounted(() => { }) onBeforeUnmount(() => { - // //console.log(`the component is now unmounted.`) + // //console.log(`the component is now unmounted.`) //clearInterval(ticker.value) ws.value.close() message.destroyAll() @@ -286,80 +274,69 @@ onBeforeUnmount(() => { EventsOff("loadingDone") }) -EventsOn("loadingDone",(data)=>{ +EventsOn("loadingDone", (data) => { message.loading("刷新股票基础数据...") GetStockList("").then(result => { stockList.value = result - options.value=result.map(item => { + options.value = result.map(item => { return { - label: item.name+" - "+item.ts_code, + label: item.name + " - " + item.ts_code, value: item.ts_code } }) }) }) -EventsOn("refresh",(data)=>{ +EventsOn("refresh", (data) => { message.success(data) }) -EventsOn("showSearch",(data)=>{ +EventsOn("showSearch", (data) => { addBTN.value = data === 1; }) -EventsOn("stock_price",(data)=>{ +EventsOn("stock_price", (data) => { updateData(data) }) -EventsOn("refreshFollowList",(data)=>{ +EventsOn("refreshFollowList", (data) => { WindowReload() - // message.loading("refresh...") - // GetFollowList().then(result => { - // followList.value = result - // for (const followedStock of result) { - // if (!stocks.value.includes(followedStock.StockCode)) { - // stocks.value.push(followedStock.StockCode) - // } - // } - // monitor() - // message.destroyAll - // }) }) -EventsOn("newChatStream",async (msg) => { +EventsOn("newChatStream", async (msg) => { ////console.log("newChatStream:->",data.airesult) data.loading = false ////console.log(msg) if (msg === "DONE") { - SaveAIResponseResult(data.code, data.name, data.airesult, data.chatId,data.question) + SaveAIResponseResult(data.code, data.name, data.airesult, data.chatId, data.question) message.info("AI分析完成!") message.destroyAll() } else { - if(msg.chatId){ - data.chatId = msg.chatId - } - if(msg.question){ - data.question = msg.question - } - if(msg.content){ - data.airesult = data.airesult + msg.content - } - if(msg.extraContent){ - data.airesult = data.airesult + msg.extraContent - } + if (msg.chatId) { + data.chatId = msg.chatId + } + if (msg.question) { + data.question = msg.question + } + if (msg.content) { + data.airesult = data.airesult + msg.content + } + if (msg.extraContent) { + data.airesult = data.airesult + msg.extraContent + } } }) -EventsOn("changeTab" ,async (msg) => { +EventsOn("changeTab", async (msg) => { //console.log("changeTab",msg) - currentGroupId.value=msg.ID + currentGroupId.value = msg.ID updateTab(currentGroupId.value) }) -EventsOn("updateVersion",async (msg) => { +EventsOn("updateVersion", async (msg) => { const githubTimeStr = msg.published_at; // 创建一个 Date 对象 const utcDate = new Date(githubTimeStr); @@ -392,10 +369,10 @@ EventsOn("updateVersion",async (msg) => { 'text-align': 'left', 'font-size': '14px', } - }, { default: () => msg.commit?.message }) + }, {default: () => msg.commit?.message}) }, duration: 5000, - meta: "发布时间:"+formattedDate, + meta: "发布时间:" + formattedDate, action: () => { return h(NButton, { type: 'primary', @@ -403,12 +380,12 @@ EventsOn("updateVersion",async (msg) => { onClick: () => { window.open(msg.html_url) } - }, { default: () => '查看' }) + }, {default: () => '查看'}) } }) }) -EventsOn("warnMsg",async (msg) => { +EventsOn("warnMsg", async (msg) => { notify.error({ avatar: () => h(NAvatar, { @@ -424,7 +401,7 @@ EventsOn("warnMsg",async (msg) => { 'text-align': 'left', 'font-size': '14px', } - }, { default: () => msg }) + }, {default: () => msg}) }, }) }) @@ -449,34 +426,36 @@ function isTradingTime() { return false; } -function AddStock(){ +function AddStock() { if (!data?.code) { message.error("请输入有效股票代码"); return; } if (!stocks.value.includes(data.code)) { - Follow(data.code).then(result => { - if(result==="关注成功"){ - stocks.value.push(data.code) - message.success(result) - GetFollowList(currentGroupId.value).then(result => { - followList.value = result - }) - monitor(); - }else{ - message.error(result) + Follow(data.code).then(result => { + if (result === "关注成功") { + if (data.code.startsWith("us")) { + data.code= "gb_" + data.code.replace("us", "").toLowerCase() } - }) - }else{ + stocks.value.push(data.code) + message.success(result) + GetFollowList(currentGroupId.value).then(result => { + followList.value = result + }) + monitor(); + } else { + message.error(result) + } + }) + } else { message.error("已经关注了") } } - -function removeMonitor(code,name,key) { +function removeMonitor(code, name, key) { //console.log("removeMonitor",name,code,key) - stocks.value.splice(stocks.value.indexOf(code),1) + stocks.value.splice(stocks.value.indexOf(code), 1) //console.log("removeMonitor-key",key) //console.log("removeMonitor-v",results.value[key]) @@ -489,61 +468,58 @@ function removeMonitor(code,name,key) { } -function SendDanmu(){ +function SendDanmu() { //danmus.value.push(data.name) //console.log("SendDanmu",data.name) //console.log("SendDanmu-readyState", ws.value.readyState) ws.value.send(data.name) } -function getStockList(value){ +function getStockList(value) { - - // //console.log("getStockList",value) + // //console.log("getStockList",value) let result; - result=stockList.value.filter(item => item.name.includes(value)||item.ts_code.includes(value)) - options.value=result.map(item => { + result = stockList.value.filter(item => item.name.includes(value) || item.ts_code.includes(value)) + options.value = result.map(item => { return { - label: item.name+" - "+item.ts_code, + label: item.name + " - " + item.ts_code, value: item.ts_code } }) - if(value&&value.indexOf("-")<=0){ - data.code=value + if (value && value.indexOf("-") <= 0) { + data.code = value } //console.log("getStockList-options",data.code) - if(data.code){ - let findId=data.code - if(findId.startsWith("us")){ - findId="gb_"+ findId.replace("us", "").toLowerCase() + if (data.code) { + let findId = data.code + if (findId.startsWith("us")) { + findId = "gb_" + findId.replace("us", "").toLowerCase() } blinkBorder(findId) } - - } -function blinkBorder(findId){ +function blinkBorder(findId) { // 获取要滚动到的元素 let element = document.getElementById(findId); //console.log("blinkBorder",findId,element) if (element) { // 滚动到该元素 - element.scrollIntoView({ behavior: 'smooth'}); - const pelement = document.getElementById(findId +'_gi'); - if(pelement){ + element.scrollIntoView({behavior: 'smooth'}); + const pelement = document.getElementById(findId + '_gi'); + if (pelement) { // 添加闪烁效果 pelement.classList.add('blink-border'); // 3秒后移除闪烁效果 setTimeout(() => { pelement.classList.remove('blink-border'); - }, 1000*5); - }else{ + }, 1000 * 5); + } else { console.error(`Element with ID ${findId}_gi not found`); } } @@ -552,57 +528,60 @@ function blinkBorder(findId){ async function updateData(result) { ////console.log("stock_price",result['日期'],result['时间'],result['股票代码'],result['股票名称'],result['当前价格'],result['盘前盘后']) - if(result["当前价格"]<=0){ - result["当前价格"]=result["卖一报价"] + if (result["当前价格"] <= 0) { + result["当前价格"] = result["卖一报价"] } - if (result.changePercent>0) { - result.type="error" - result.color="#E88080" - }else if (result.changePercent<0) { - result.type="success" - result.color="#63E2B7" - }else { - result.type="default" - result.color="#FFFFFF" + if (result.changePercent > 0) { + result.type = "error" + result.color = "#E88080" + } else if (result.changePercent < 0) { + result.type = "success" + result.color = "#63E2B7" + } else { + result.type = "default" + result.color = "#FFFFFF" } - if(result.profitAmount>0){ - result.profitType="error" - }else if(result.profitAmount<0){ - result.profitType="success" - } - if(result["当前价格"]){ - if(result.alarmChangePercent>0&&Math.abs(result.changePercent)>=result.alarmChangePercent){ - SendMessage(result,1) - } - - if(result.alarmPrice>0&&result["当前价格"]>=result.alarmPrice){ - SendMessage(result,2) - } - - if(result.costPrice>0&&result["当前价格"]>=result.costPrice){ - SendMessage(result,3) - } + if (result.profitAmount > 0) { + result.profitType = "error" + } else if (result.profitAmount < 0) { + result.profitType = "success" + } + if (result["当前价格"]) { + if (result.alarmChangePercent > 0 && Math.abs(result.changePercent) >= result.alarmChangePercent) { + SendMessage(result, 1) } - //result.key=result.sort - result.key=GetSortKey(result.sort,result["股票代码"]) - results.value[GetSortKey(result.sort,result["股票代码"])]=result - if(!stocks.value.includes(result["股票代码"])) { + if (result.alarmPrice > 0 && result["当前价格"] >= result.alarmPrice) { + SendMessage(result, 2) + } + + if (result.costPrice > 0 && result["当前价格"] >= result.costPrice) { + SendMessage(result, 3) + } + } + + // result.key=result.sort + results.value = Object.fromEntries( + Object.entries(results.value).filter( + ([key]) => !key.includes(result["股票代码"]) + )); + + result.key = GetSortKey(result.sort, result["股票代码"]) + results.value[result.key] = result + if (!stocks.value.includes(result["股票代码"])) { delete results.value[result.key] } - - ////console.log("updateData",result) } async function monitor() { - if(stocks.value&&stocks.value.length===0){ - showPopover.value=true + if (stocks.value && stocks.value.length === 0) { + showPopover.value = true } for (let code of stocks.value) { - // //console.log(code) + Greet(code).then(result => { updateData(result) }) @@ -610,51 +589,67 @@ async function monitor() { } -function GetSortKey(sort,code){ - let sortKey= padStart(sort,8,'0')+"_"+code - ////console.log("GetSortKey:",sortKey) +function GetSortKey(sort, code) { + let sortKey = padStart(sort, 8, '0') + "_" + code return sortKey } function onSelect(item) { ////console.log("onSelect",item) - if(item.indexOf("-")>0){ - item=item.split("-")[1].toLowerCase() + if (item.indexOf("-") > 0) { + item = item.split("-")[1].toLowerCase() } - if(item.indexOf(".")>0){ - data.code=item.split(".")[1].toLowerCase()+item.split(".")[0] + if (item.indexOf(".") > 0) { + data.code = item.split(".")[1].toLowerCase() + item.split(".")[0] } } -function search(code,name){ +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 search(code, name) { setTimeout(() => { //window.open("https://xueqiu.com/S/"+code) //window.open("https://www.cls.cn/stock?code="+code) //window.open("https://quote.eastmoney.com/"+code+".html") //window.open("https://finance.sina.com.cn/realstock/company/"+code+"/nc.shtml") - window.open("https://www.iwencai.com/unifiedwap/result?w="+name) + //window.open("https://www.iwencai.com/unifiedwap/result?w=" + name) //window.open("https://www.iwencai.com/chat/?question="+code) + + openCenteredWindow("https://www.iwencai.com/unifiedwap/result?w=" + name,1000,800) + }, 500) } -function setStock(code,name){ - let res=followList.value.filter(item => item.StockCode===code) - ////console.log("res:",res) - formModel.value.name=name - formModel.value.code=code - formModel.value.volume=res[0].Volume?res[0].Volume:0 - formModel.value.costPrice=res[0].CostPrice - formModel.value.alarm=res[0].AlarmChangePercent - formModel.value.alarmPrice=res[0].AlarmPrice - formModel.value.sort=res[0].Sort - formModel.value.cron=res[0].Cron - modalShow.value=true + +function setStock(code, name) { + let res = followList.value.filter(item => item.StockCode === code) + ////console.log("res:",res) + formModel.value.name = name + formModel.value.code = code + formModel.value.volume = res[0].Volume ? res[0].Volume : 0 + formModel.value.costPrice = res[0].CostPrice + formModel.value.alarm = res[0].AlarmChangePercent + formModel.value.alarmPrice = res[0].AlarmPrice + formModel.value.sort = res[0].Sort + formModel.value.cron = res[0].Cron + modalShow.value = true } -function clearFeishi(){ + +function clearFeishi() { //console.log("clearFeishi") clearInterval(feishiInterval.value) } + function showFsChart(code, name) { data.name = name data.code = code @@ -664,14 +659,14 @@ function showFsChart(code, name) { const priceData = result.priceData let category = [] let price = [] - let openprice=0 - let closeprice=0 + let openprice = 0 + let closeprice = 0 let volume = [] let volumeRate = [] let min = 0 let max = 0 - openprice=priceData[0].price - closeprice=priceData[priceData.length-1].price + openprice = priceData[0].price + closeprice = priceData[priceData.length - 1].price for (let i = 0; i < priceData.length; i++) { category.push(priceData[i].time) price.push(priceData[i].price) @@ -682,8 +677,8 @@ function showFsChart(code, name) { max = priceData[i].price } if (i > 0) { - let b=priceData[i].volume - priceData[i - 1].volume - volumeRate.push(((b-volume[i-1])/volume[i-1]*100).toFixed(2)) + let b = priceData[i].volume - priceData[i - 1].volume + volumeRate.push(((b - volume[i - 1]) / volume[i - 1] * 100).toFixed(2)) volume.push(b) } else { volume.push(priceData[i].volume) @@ -693,7 +688,7 @@ function showFsChart(code, name) { let option = { title: { - subtext: "["+result.date+"] 开盘:"+openprice+" 最新:"+closeprice+" 最高:"+max+" 最低:"+min, + subtext: "[" + result.date + "] 开盘:" + openprice + " 最新:" + closeprice + " 最高:" + max + " 最低:" + min, left: 'center', top: '10', textStyle: { @@ -730,13 +725,13 @@ function showFsChart(code, name) { } }, xAxis: [ - { - type: 'category', - data: category, - axisLabel: { - show: false - } - }, + { + type: 'category', + data: category, + axisLabel: { + show: false + } + }, { gridIndex: 1, type: 'category', @@ -765,8 +760,8 @@ function showFsChart(code, name) { show: false }, name: "股价", - min: (min - min*0.01).toFixed(2), - max: (max + max*0.01).toFixed(2), + min: (min - min * 0.01).toFixed(2), + max: (max + max * 0.01).toFixed(2), minInterval: 0.01, type: 'value' }, @@ -784,7 +779,7 @@ function showFsChart(code, name) { ], visualMap: { type: 'piecewise', - seriesIndex:0, + seriesIndex: 0, top: 0, left: 10, orient: 'horizontal', @@ -827,13 +822,13 @@ function showFsChart(code, name) { type: 'line', smooth: false, showSymbol: false, - lineStyle: { + lineStyle: { width: 3 }, markPoint: { symbol: 'arrow', - symbolRotate:90, - symbolSize: [10,20], + symbolRotate: 90, + symbolSize: [10, 20], symbolOffset: [10, 0], itemStyle: { color: '#FC290D' @@ -849,24 +844,24 @@ function showFsChart(code, name) { markLine: { symbol: 'none', data: [ - { type: 'average', name: 'Average' }, - { - lineStyle:{ - color: '#FFCB00', - width: 0.5 - }, - yAxis: openprice, - name: '开盘价' + {type: 'average', name: 'Average'}, + { + lineStyle: { + color: '#FFCB00', + width: 0.5 }, - { - yAxis: closeprice , - symbol: 'none', - lineStyle:{ - color: 'red', - width: 0.5 - }, - } - ] + yAxis: openprice, + name: '开盘价' + }, + { + yAxis: closeprice, + symbol: 'none', + lineStyle: { + color: 'red', + width: 0.5 + }, + } + ] }, }, { @@ -883,30 +878,30 @@ function showFsChart(code, name) { }) } -function showFenshi(code,name,changePercent){ - data.code=code - data.name=name - data.changePercent=changePercent - data.fenshiURL='http://image.sinajs.cn/newchart/min/n/'+data.code+'.gif'+"?t="+Date.now() +function showFenshi(code, name, changePercent) { + data.code = code + data.name = name + data.changePercent = changePercent + data.fenshiURL = 'http://image.sinajs.cn/newchart/min/n/' + data.code + '.gif' + "?t=" + Date.now() - if(code.startsWith('hk')){ - data.fenshiURL='http://image.sinajs.cn/newchart/hk_stock/min/'+data.code.replace("hk","")+'.gif'+"?t="+Date.now() + if (code.startsWith('hk')) { + data.fenshiURL = 'http://image.sinajs.cn/newchart/hk_stock/min/' + data.code.replace("hk", "") + '.gif' + "?t=" + Date.now() } - if(code.startsWith('gb_')){ - data.fenshiURL='http://image.sinajs.cn/newchart/usstock/min/'+data.code.replace("gb_","")+'.gif'+"?t="+Date.now() + if (code.startsWith('gb_')) { + data.fenshiURL = 'http://image.sinajs.cn/newchart/usstock/min/' + data.code.replace("gb_", "") + '.gif' + "?t=" + Date.now() } - modalShow2.value=true + modalShow2.value = true } -function handleFeishi(){ +function handleFeishi() { showFsChart(data.code, data.name); - feishiInterval.value=setInterval(() => { + feishiInterval.value = setInterval(() => { showFsChart(data.code, data.name); - }, 1000*10) + }, 1000 * 10) } -function calculateMA(dayCount,values) { +function calculateMA(dayCount, values) { var result = []; for (var i = 0, len = values.length; i < len; i++) { if (i < dayCount) { @@ -921,39 +916,40 @@ function calculateMA(dayCount,values) { } return result; } -function handleKLine(){ - GetStockKLine(data.code,data.name,365).then(result => { + +function handleKLine() { + GetStockKLine(data.code, data.name, 365).then(result => { //console.log("GetStockKLine",result) const chart = echarts.init(kLineChartRef.value); const categoryData = []; const values = []; - const volumns=[]; + const volumns = []; for (let i = 0; i < result.length; i++) { - let resultElement=result[i] + let resultElement = result[i] //console.log("resultElement:{}",resultElement) categoryData.push(resultElement.day) - let flag=resultElement.close>resultElement.open?1:-1 + let flag = resultElement.close > resultElement.open ? 1 : -1 values.push([ resultElement.open, resultElement.close, resultElement.low, resultElement.high ]) - volumns.push([i,resultElement.volume/10000,flag]) + volumns.push([i, resultElement.volume / 10000, flag]) } ////console.log("categoryData",categoryData) ////console.log("values",values) let option = { darkMode: data.darkTheme, //backgroundColor: '#1c1c1c', - // color:['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'], + // color:['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'], animation: false, legend: { bottom: 10, left: 'center', data: ['日K', 'MA5', 'MA10', 'MA20', 'MA30'], textStyle: { - color: data.darkTheme?'#ccc':'#456' + color: data.darkTheme ? '#ccc' : '#456' }, }, tooltip: { @@ -967,19 +963,19 @@ function handleKLine(){ } }, borderWidth: 2, - borderColor: data.darkTheme?'#456':'#ccc', - backgroundColor: data.darkTheme?'#456':'#fff', + borderColor: data.darkTheme ? '#456' : '#ccc', + backgroundColor: data.darkTheme ? '#456' : '#fff', padding: 10, textStyle: { - color: data.darkTheme?'#ccc':'#456' + color: data.darkTheme ? '#ccc' : '#456' }, formatter: function (params) {//修改鼠标划过显示为中文 //console.log("params",params) - let volum=params[5].data;//ma5的值 - let ma5=params[1].data;//ma5的值 - let ma10=params[2].data;//ma10的值 - let ma20=params[3].data;//ma20的值 - let ma30=params[4].data;//ma30的值 + let volum = params[5].data;//ma5的值 + let ma5 = params[1].data;//ma5的值 + let ma10 = params[2].data;//ma10的值 + let ma20 = params[3].data;//ma20的值 + let ma30 = params[4].data;//ma30的值 params = params[0];//开盘收盘最低最高数据汇总 let currentItemData = params.data; @@ -1046,8 +1042,8 @@ function handleKLine(){ type: 'category', data: categoryData, boundaryGap: false, - axisLine: { onZero: false }, - splitLine: { show: false }, + axisLine: {onZero: false}, + splitLine: {show: false}, min: 'dataMin', max: 'dataMax', axisPointer: { @@ -1059,10 +1055,10 @@ function handleKLine(){ gridIndex: 1, data: categoryData, boundaryGap: false, - axisLine: { onZero: false }, - axisTick: { show: false }, - splitLine: { show: false }, - axisLabel: { show: false }, + axisLine: {onZero: false}, + axisTick: {show: false}, + splitLine: {show: false}, + axisLabel: {show: false}, min: 'dataMin', max: 'dataMax' } @@ -1078,10 +1074,10 @@ function handleKLine(){ scale: true, gridIndex: 1, splitNumber: 2, - axisLabel: { show: false }, - axisLine: { show: false }, - axisTick: { show: false }, - splitLine: { show: false } + axisLabel: {show: false}, + axisLine: {show: false}, + axisTick: {show: false}, + splitLine: {show: false} } ], dataZoom: [ @@ -1109,8 +1105,8 @@ function handleKLine(){ itemStyle: { color: upColor, color0: downColor, - // borderColor: upBorderColor, - // borderColor0: downBorderColor + // borderColor: upBorderColor, + // borderColor0: downBorderColor }, markPoint: { label: { @@ -1191,7 +1187,7 @@ function handleKLine(){ { name: 'MA5', type: 'line', - data: calculateMA(5,values), + data: calculateMA(5, values), smooth: true, showSymbol: false, lineStyle: { @@ -1201,7 +1197,7 @@ function handleKLine(){ { name: 'MA10', type: 'line', - data: calculateMA(10,values), + data: calculateMA(10, values), smooth: true, showSymbol: false, lineStyle: { @@ -1211,7 +1207,7 @@ function handleKLine(){ { name: 'MA20', type: 'line', - data: calculateMA(20,values), + data: calculateMA(20, values), smooth: true, showSymbol: false, lineStyle: { @@ -1221,7 +1217,7 @@ function handleKLine(){ { name: 'MA30', type: 'line', - data: calculateMA(30,values), + data: calculateMA(30, values), smooth: true, showSymbol: false, lineStyle: { @@ -1241,58 +1237,57 @@ function handleKLine(){ ] }; chart.setOption(option); - chart.on('click',{seriesName:'日K'}, function(params) { + chart.on('click', {seriesName: '日K'}, function (params) { //console.log("click:",params); }); }) } -function showMoney(code,name){ - data.code=code - data.name=name - modalShow5.value=true + +function showMoney(code, name) { + data.code = code + data.name = name + modalShow5.value = true } -function showK(code,name){ - data.code=code - data.name=name - data.kURL='http://image.sinajs.cn/newchart/daily/n/'+data.code+'.gif'+"?t="+Date.now() - if(code.startsWith('hk')){ - data.kURL='http://image.sinajs.cn/newchart/hk_stock/daily/'+data.code.replace("hk","")+'.gif'+"?t="+Date.now() +function showK(code, name) { + data.code = code + data.name = name + data.kURL = 'http://image.sinajs.cn/newchart/daily/n/' + data.code + '.gif' + "?t=" + Date.now() + if (code.startsWith('hk')) { + data.kURL = 'http://image.sinajs.cn/newchart/hk_stock/daily/' + data.code.replace("hk", "") + '.gif' + "?t=" + Date.now() } - if(code.startsWith('gb_')){ - data.kURL='http://image.sinajs.cn/newchart/usstock/daily/'+data.code.replace("gb_","")+'.gif'+"?t="+Date.now() + if (code.startsWith('gb_')) { + data.kURL = 'http://image.sinajs.cn/newchart/usstock/daily/' + data.code.replace("gb_", "") + '.gif' + "?t=" + Date.now() } - modalShow3.value=true + modalShow3.value = true //https://image.sinajs.cn/newchart/usstock/daily/dji.gif //https://image.sinajs.cn/newchart/hk_stock/daily/06030.gif?1740729404273 } - - -function updateCostPriceAndVolumeNew(code,price,volume,alarm,formModel){ - - if(formModel.sort){ - SetStockSort(formModel.sort,code).then(result => { +function updateCostPriceAndVolumeNew(code, price, volume, alarm, formModel) { + if (formModel.sort) { + SetStockSort(formModel.sort, code).then(result => { //message.success(result) }) } - if(formModel.cron){ - SetStockAICron(formModel.cron,code).then(result => { + if (formModel.cron) { + SetStockAICron(formModel.cron, code).then(result => { //message.success(result) }) } - if(alarm||formModel.alarmPrice){ - SetAlarmChangePercent(alarm,formModel.alarmPrice,code).then(result => { + if (alarm || formModel.alarmPrice) { + SetAlarmChangePercent(alarm, formModel.alarmPrice, code).then(result => { //message.success(result) }) } - SetCostPriceAndVolume(code,price,volume).then(result => { - modalShow.value=false + SetCostPriceAndVolume(code, price, volume).then(result => { + modalShow.value = false message.success(result) GetFollowList(currentGroupId.value).then(result => { followList.value = result + stocks.value = [] for (const followedStock of result) { if (!stocks.value.includes(followedStock.StockCode)) { stocks.value.push(followedStock.StockCode) @@ -1304,73 +1299,75 @@ function updateCostPriceAndVolumeNew(code,price,volume,alarm,formModel){ }) } -function fullscreen(){ - if(data.fullscreen){ +function fullscreen() { + if (data.fullscreen) { WindowUnfullscreen() - }else{ + } else { WindowFullscreen() } - data.fullscreen=!data.fullscreen + data.fullscreen = !data.fullscreen } //type 报警类型: 1 涨跌报警;2 股价报警 3 成本价报警 -function SendMessage(result,type){ - let typeName=getTypeName(type) - let img='http://image.sinajs.cn/newchart/min/n/'+result["股票代码"]+'.gif'+"?t="+Date.now() - let markdown="### go-stock ["+typeName+"]\n\n"+ - "### "+result["股票名称"]+"("+result["股票代码"]+")\n" + - "- 当前价格: "+result["当前价格"]+" "+result.changePercent+"%\n" + - "- 最高价: "+result["今日最高价"]+" "+result.highRate+"\n" + - "- 最低价: "+result["今日最低价"]+" "+result.lowRate+"\n" + - "- 昨收价: "+result["昨日收盘价"]+"\n" + - "- 今开价: "+result["今日开盘价"]+"\n" + - "- 成本价: "+result.costPrice+" "+result.profit+"% "+result.profitAmount+" ¥\n" + - "- 成本数量: "+result.costVolume+"股\n" + - "- 日期: "+result["日期"]+" "+result["时间"]+"\n\n"+ - "![image]("+img+")\n" - let title=result["股票名称"]+"("+result["股票代码"]+") "+result["当前价格"]+" "+result.changePercent +function SendMessage(result, type) { + let typeName = getTypeName(type) + let img = 'http://image.sinajs.cn/newchart/min/n/' + result["股票代码"] + '.gif' + "?t=" + Date.now() + let markdown = "### go-stock [" + typeName + "]\n\n" + + "### " + result["股票名称"] + "(" + result["股票代码"] + ")\n" + + "- 当前价格: " + result["当前价格"] + " " + result.changePercent + "%\n" + + "- 最高价: " + result["今日最高价"] + " " + result.highRate + "\n" + + "- 最低价: " + result["今日最低价"] + " " + result.lowRate + "\n" + + "- 昨收价: " + result["昨日收盘价"] + "\n" + + "- 今开价: " + result["今日开盘价"] + "\n" + + "- 成本价: " + result.costPrice + " " + result.profit + "% " + result.profitAmount + " ¥\n" + + "- 成本数量: " + result.costVolume + "股\n" + + "- 日期: " + result["日期"] + " " + result["时间"] + "\n\n" + + "![image](" + img + ")\n" + let title = result["股票名称"] + "(" + result["股票代码"] + ") " + result["当前价格"] + " " + result.changePercent - let msg='{' + + let msg = '{' + ' "msgtype": "markdown",' + ' "markdown": {' + - ' "title":"['+typeName+"]"+title+'",' + - ' "text": "'+markdown+'"' + + ' "title":"[' + typeName + "]" + title + '",' + + ' "text": "' + markdown + '"' + ' },' + ' "at": {' + ' "isAtAll": true' + ' }' + ' }' - // SendDingDingMessage(msg,result["股票代码"]) - SendDingDingMessageByType(msg,result["股票代码"],type) + // SendDingDingMessage(msg,result["股票代码"]) + SendDingDingMessageByType(msg, result["股票代码"], type) } -function aiReCheckStock(stock,stockCode) { - data.modelName="" - data.airesult="" - data.time="" - data.name=stock - data.code=stockCode - data.loading=true - modalShow4.value=true - message.loading("ai检测中...",{ + +function aiReCheckStock(stock, stockCode) { + data.modelName = "" + data.airesult = "" + data.time = "" + data.name = stock + data.code = stockCode + data.loading = true + modalShow4.value = true + message.loading("ai检测中...", { duration: 0, }) // //message.info("sysPromptId:"+data.sysPromptId) - NewChatStream(stock,stockCode,data.question,data.sysPromptId) + NewChatStream(stock, stockCode, data.question, data.sysPromptId) } -function aiCheckStock(stock,stockCode){ + +function aiCheckStock(stock, stockCode) { GetAIResponseResult(stockCode).then(result => { - if(result.content){ - data.modelName=result.modelName - data.chatId=result.chatId - data.question=result.question - data.name=stock - data.code=stockCode - data.loading=false - modalShow4.value=true - data.airesult=result.content + if (result.content) { + data.modelName = result.modelName + data.chatId = result.chatId + data.question = result.question + data.name = stock + data.code = stockCode + data.loading = false + modalShow4.value = true + data.airesult = result.content const date = new Date(result.CreatedAt); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); @@ -1378,27 +1375,26 @@ function aiCheckStock(stock,stockCode){ const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); - data.time=`${year}-${month}-${day} ${hours}:${minutes}:${seconds}` - }else{ - data.modelName="" - data.question="" - data.airesult="" - data.time="" - data.name=stock - data.code=stockCode - data.loading=true - modalShow4.value=true - message.loading("ai检测中...",{ + data.time = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` + } else { + data.modelName = "" + data.question = "" + data.airesult = "" + data.time = "" + data.name = stock + data.code = stockCode + data.loading = true + modalShow4.value = true + message.loading("ai检测中...", { duration: 0, }) - NewChatStream(stock,stockCode,"",data.sysPromptId) + NewChatStream(stock, stockCode, "", data.sysPromptId) } }) } -function getTypeName(type){ - switch (type) - { +function getTypeName(type) { + switch (type) { case 1: return "涨跌报警" case 2: @@ -1424,28 +1420,28 @@ window.onerror = function (msg, source, lineno, colno, error) { lineno: lineno, colno: colno, error: error ? error.stack : null, - data:data, - results:results, - followList:followList, - stockList:stockList, - stocks:stocks, - formModel:formModel, + data: data, + results: results, + followList: followList, + stockList: stockList, + stocks: stocks, + formModel: formModel, }); - message.error("发生错误:"+msg) + message.error("发生错误:" + msg) return true; }; -function saveAsImage(name,code) { +function saveAsImage(name, code) { const element = document.querySelector('.md-editor-preview'); if (element) { - html2canvas(element,{ + html2canvas(element, { useCORS: true, // 解决跨域图片问题 scale: 2, // 提高截图质量 allowTaint: true, // 允许跨域图片 }).then(canvas => { const link = document.createElement('a'); link.href = canvas.toDataURL('image/png'); - link.download = name+"["+code+']-ai-analysis-result.png'; + link.download = name + "[" + code + ']-ai-analysis-result.png'; link.click(); }); } else { @@ -1461,13 +1457,15 @@ async function copyToClipboard() { message.error('复制失败: ' + err); } } -function saveAsMarkdown(){ - SaveAsMarkdown(data.code,data.name).then(result => { + +function saveAsMarkdown() { + SaveAsMarkdown(data.code, data.name).then(result => { message.success(result) }) } + function saveAsMarkdown_old() { - const blob = new Blob([data.airesult], { type: 'text/markdown;charset=utf-8' }); + const blob = new Blob([data.airesult], {type: 'text/markdown;charset=utf-8'}); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `${data.name}[${data.code}]-${data.time}ai-analysis-result.md`; @@ -1475,6 +1473,7 @@ function saveAsMarkdown_old() { URL.revokeObjectURL(link.href); link.remove() } + function getHtml(ref) { if (ref.value) { // 获取 MdPreview 组件的根元素 @@ -1492,7 +1491,7 @@ async function saveAsWord() { // 将富文本内容拼接为一个完整的html const html = getHtml(mdPreviewRef) const tipsHtml = getHtml(tipsRef) - const value = ` + const value = ` ${html}


@@ -1506,7 +1505,7 @@ AI赋能股票分析:自选股行情获取,成本盈亏展示,涨跌报警

` // landscape就是横着的,portrait是竖着的,默认是竖屏portrait。 - const blob = await asBlob(value, { orientation: 'portrait' }) + const blob = await asBlob(value, {orientation: 'portrait'}) const a = document.createElement('a') a.href = URL.createObjectURL(blob) a.download = `${data.name}[${data.code}]-ai-analysis-result.docx`; @@ -1516,8 +1515,8 @@ AI赋能股票分析:自选股行情获取,成本盈亏展示,涨跌报警 a.remove() } -function share(code,name){ - ShareAnalysis(code,name).then(msg => { +function share(code, name) { + ShareAnalysis(code, name).then(msg => { //message.info(msg) notify.info({ avatar: () => @@ -1527,90 +1526,96 @@ function share(code,name){ src: icon.value }), title: '分享到社区', - duration:1000*30, + duration: 1000 * 30, content: () => { return h('div', { style: { 'text-align': 'left', 'font-size': '14px', } - }, { default: () => msg }) + }, {default: () => msg}) }, }) }) } -const addTabModel=ref({ + +const addTabModel = ref({ name: '', sort: 1, }) -const addTabPane=ref(false) -function addTab(){ - addTabPane.value=true +const addTabPane = ref(false) + +function addTab() { + addTabPane.value = true } -function saveTabPane(){ + +function saveTabPane() { AddGroup(addTabModel.value).then(result => { message.info(result) - addTabPane.value=false + addTabPane.value = false GetGroupList().then(result => { - groupList.value=result + groupList.value = result }) }) } -function AddStockGroupInfo(groupId,code,name){ - if(code.startsWith("gb_")){ - code="us"+ code.replace("gb_", "").toLowerCase() + +function AddStockGroupInfo(groupId, code, name) { + if (code.startsWith("gb_")) { + code = "us" + code.replace("gb_", "").toLowerCase() } - AddStockGroup(groupId,code).then(result => { + AddStockGroup(groupId, code).then(result => { message.info(result) GetGroupList().then(result => { - groupList.value=result + groupList.value = result }) }) } -function updateTab(name){ - currentGroupId.value=Number(name) + +function updateTab(name) { + currentGroupId.value = Number(name) GetFollowList(currentGroupId.value).then(result => { - stocks.value=[] - //console.log("GetFollowList",result) + stocks.value = [] followList.value = result for (const followedStock of result) { - if(followedStock.StockCode.startsWith("us")){ - followedStock.StockCode="gb_"+ followedStock.StockCode.replace("us", "").toLowerCase() + if (followedStock.StockCode.startsWith("us")) { + followedStock.StockCode = "gb_" + followedStock.StockCode.replace("us", "").toLowerCase() } - ////console.log("followList",followedStock.StockCode) - stocks.value.push(followedStock.StockCode) + ////console.log("followList",followedStock.StockCode) + stocks.value.push(followedStock.StockCode) } monitor() message.destroyAll() }) } -function delTab(name){ - let infos=groupList.value=groupList.value.filter(item => item.ID === Number(name)) + +function delTab(name) { + let infos = groupList.value = groupList.value.filter(item => item.ID === Number(name)) dialog.create({ title: '删除分组', type: 'warning', - content: '确定要删除['+infos[0].name+']分组吗?分组数据将不能恢复哟!', + content: '确定要删除[' + infos[0].name + ']分组吗?分组数据将不能恢复哟!', positiveText: '确定', negativeText: '取消', onPositiveClick: () => { RemoveGroup(name).then(result => { message.info(result) GetGroupList().then(result => { - groupList.value=result + groupList.value = result }) }) } }) } -function delStockGroup(code,name,groupId){ - RemoveStockGroup(code,name,groupId).then(result => { + +function delStockGroup(code, name, groupId) { + RemoveStockGroup(code, name, groupId).then(result => { updateTab(groupId) message.info(result) }) } -function searchNotice(stockCode){ +function searchNotice(stockCode) { router.push({ name: 'market', query: { @@ -1619,7 +1624,8 @@ function searchNotice(stockCode){ }, }) } -function searchStockReport(stockCode){ + +function searchStockReport(stockCode) { router.push({ name: 'market', query: { @@ -1631,384 +1637,447 @@ function searchStockReport(stockCode){ diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 4e2757e..a807d0e 100644 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -15,6 +15,8 @@ export function AnalyzeSentiment(arg1:string):Promise; export function CheckUpdate():Promise; +export function ClsCalendar():Promise>; + export function DelPrompt(arg1:number):Promise; export function EMDictCode(arg1:string):Promise>; @@ -65,8 +67,16 @@ export function GlobalStockIndexes():Promise>; export function Greet(arg1:string):Promise; +export function HotEvent(arg1:number):Promise; + +export function HotStock(arg1:string):Promise; + +export function HotTopic(arg1:number):Promise>; + export function IndustryResearchReport(arg1:string):Promise>; +export function InvestCalendarTimeLine(arg1:string):Promise>; + export function LongTigerRank(arg1:string):Promise; export function NewChatStream(arg1:string,arg2:string,arg3:string,arg4:any):Promise; @@ -83,6 +93,8 @@ export function SaveAIResponseResult(arg1:string,arg2:string,arg3:string,arg4:st export function SaveAsMarkdown(arg1:string,arg2:string):Promise; +export function SearchStock(arg1:string):Promise>; + export function SendDingDingMessage(arg1:string,arg2:string):Promise; export function SendDingDingMessageByType(arg1:string,arg2:string,arg3:number):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 27d7441..da3813b 100644 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -26,6 +26,10 @@ export function CheckUpdate() { return window['go']['main']['App']['CheckUpdate'](); } +export function ClsCalendar() { + return window['go']['main']['App']['ClsCalendar'](); +} + export function DelPrompt(arg1) { return window['go']['main']['App']['DelPrompt'](arg1); } @@ -126,10 +130,26 @@ export function 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); } @@ -162,6 +182,10 @@ export function 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) { return window['go']['main']['App']['SendDingDingMessage'](arg1, arg2); }