Merge pull request #84 from ArvinLovegood/dev

合并
This commit is contained in:
SparkMemory 2025-07-01 08:46:43 +08:00 committed by GitHub
commit 6116e9287a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 1927 additions and 739 deletions

View File

@ -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: '18.x' node-version: '20.x'

View File

@ -27,15 +27,16 @@
### 💬 支持大模型/平台 ### 💬 支持大模型/平台
| 模型 | 状态 | 备注 | | 模型 | 状态 | 备注 |
| --- | --- |-----------------------------------------------------------------------------------------------------------------------------------------------------| | --- | --- |---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [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://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) |
### <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)
@ -46,6 +47,8 @@
## 🧩 重大功能开发计划 ## 🧩 重大功能开发计划
| 功能说明 | 状态 | 备注 | | 功能说明 | 状态 | 备注 |
|-----------------|----|----------------------------------------------------------------------------------------------------------| |-----------------|----|----------------------------------------------------------------------------------------------------------|
| 股票分析知识库 | 🚧 | 未来计划 |
| Ai智能选股 | 🚧 | Ai智能选股功能开发中(下半年重点开发计划) |
| ETF支持 | 🚧 | ETF数据支持 (目前可以查看净值和估值) | | ETF支持 | 🚧 | ETF数据支持 (目前可以查看净值和估值) |
| 美股支持 | ✅ | 美股数据支持 | | 美股支持 | ✅ | 美股数据支持 |
| 港股支持 | ✅ | 港股数据支持 | | 港股支持 | ✅ | 港股数据支持 |
@ -54,7 +57,9 @@
| 不再强制依赖Chrome浏览器 | ✅ | 默认使用edge浏览器抓取新闻资讯 | | 不再强制依赖Chrome浏览器 | ✅ | 默认使用edge浏览器抓取新闻资讯 |
## 👀 更新日志 ## 👀 更新日志
### 2025.06.30 添加指标选股功能
### 2025.06.27 添加财经日历和重大事件时间轴功能
### 2025.06.25 添加热门股票、事件和话题功能
### 2025.06.18 更新内置股票基础数据,软件内实时市场资讯信息提醒,添加行业研究功能 ### 2025.06.18 更新内置股票基础数据,软件内实时市场资讯信息提醒,添加行业研究功能
### 2025.06.15 添加公司公告信息搜索/查看功能 ### 2025.06.15 添加公司公告信息搜索/查看功能
### 2025.06.15 添加个股研报到弹出菜单 ### 2025.06.15 添加个股研报到弹出菜单

View File

@ -31,3 +31,31 @@ func (a App) EMDictCode(code string) []any {
func (a App) AnalyzeSentiment(text string) data.SentimentResult { func (a App) AnalyzeSentiment(text string) data.SentimentResult {
return data.AnalyzeSentiment(text) 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

@ -574,3 +574,130 @@ func (m MarketNewsApi) TradingViewNews() *[]models.TVNews {
json.Unmarshal(items, TVNews) json.Unmarshal(items, TVNews)
return 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

@ -108,3 +108,46 @@ func TestTradingViewNews(t *testing.T) {
logger.SugaredLogger.Debugf("value: %+v", a) 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

@ -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
}

View File

@ -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)
//}
}

View File

@ -400,7 +400,20 @@ 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)
@ -463,15 +476,64 @@ func (receiver StockDataApi) SetAlarmChangePercent(val, alarmPrice float64, stoc
return "设置成功" return "设置成功"
} }
func (receiver StockDataApi) SetStockSort(sort int64, stockCode string) { func (receiver StockDataApi) SetStockSort(newSort 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
if err != nil { oldSort := currentStock.Sort
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_"}) {

View File

@ -317,3 +317,48 @@ type TVNews struct {
LogoId string `json:"logo_id"` LogoId string `json:"logo_id"`
} `json:"provider"` } `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"`
}

View File

@ -11,7 +11,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",
"@vicons/ionicons5": "^0.13.0", "date-fns": "^4.1.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",
@ -22,6 +22,14 @@
"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",
@ -1433,10 +1441,53 @@
"md-editor-v3": ">=5.2.0" "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": { "node_modules/@vicons/ionicons5": {
"version": "0.13.0", "version": "0.13.0",
"resolved": "https://registry.npmmirror.com/@vicons/ionicons5/-/ionicons5-0.13.0.tgz", "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": { "node_modules/@vitejs/plugin-vue": {
"version": "5.2.1", "version": "5.2.1",
@ -1667,10 +1718,10 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
}, },
"node_modules/date-fns": { "node_modules/date-fns": {
"version": "3.6.0", "version": "4.1.0",
"resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-3.6.0.tgz", "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"dev": true, "license": "MIT",
"funding": { "funding": {
"type": "github", "type": "github",
"url": "https://github.com/sponsors/kossnocorp" "url": "https://github.com/sponsors/kossnocorp"
@ -2016,6 +2067,17 @@
"vue": "^3.0.0" "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": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",

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",
"@vicons/ionicons5": "^0.13.0", "date-fns": "^4.1.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,6 +23,14 @@
"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",

View File

@ -1 +1 @@
b71b647d53bb771e87fac6e1372d9acf 2d63c3a999d797889c01d6c96451b197

View File

@ -14,7 +14,7 @@ import {createDiscreteApi,darkTheme,lightTheme , NIcon, NText,dateZhCN,zhCN} fro
import { import {
AlarmOutline, AlarmOutline,
AnalyticsOutline, AnalyticsOutline,
BarChartSharp, EaselSharp, BarChartSharp, Bonfire, BonfireOutline, EaselSharp,
ExpandOutline, Flag, ExpandOutline, Flag,
Flame, FlameSharp, InformationOutline, Flame, FlameSharp, InformationOutline,
LogoGithub, LogoGithub,
@ -28,6 +28,11 @@ import {
Wallet, WarningOutline, Wallet, WarningOutline,
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import {AnalyzeSentiment, GetConfig, GetGroupList} from "../wailsjs/go/main/App"; 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: () => '龙虎榜',} {default: () => '龙虎榜',}
), ),
key: 'market6', key: 'market6',
icon: renderIcon(Skull), icon: renderIcon(Dragon),
}, },
{ {
label: () => label: () =>
@ -262,7 +267,7 @@ const menuOptions = ref([
{default: () => '个股研报',} {default: () => '个股研报',}
), ),
key: 'market7', key: 'market7',
icon: renderIcon(NewspaperSharp), icon: renderIcon(StockOutlined),
}, },
{ {
label: () => label: () =>
@ -283,7 +288,7 @@ const menuOptions = ref([
{default: () => '公司公告',} {default: () => '公司公告',}
), ),
key: 'market8', key: 'market8',
icon: renderIcon(NewspaperSharp), icon: renderIcon(NotificationFilled),
}, },
{ {
label: () => label: () =>
@ -304,7 +309,49 @@ const menuOptions = ref([
{default: () => '行业研究',} {default: () => '行业研究',}
), ),
key: 'market9', 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),
}, },
] ]
}, },

View File

@ -0,0 +1,102 @@
<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

@ -0,0 +1,37 @@
<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

@ -0,0 +1,88 @@
<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

@ -0,0 +1,73 @@
<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

@ -0,0 +1,108 @@
<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, text: name+" "+code,
left: '20px', left: '20px',
textStyle: { textStyle: {
color: darkTheme?'#ccc':'#456' color: darkTheme?'#ccc':'#456'

View File

@ -0,0 +1,126 @@
<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

@ -135,6 +135,7 @@ 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

@ -26,6 +26,12 @@ import StockResearchReportList from "./StockResearchReportList.vue";
import StockNoticeList from "./StockNoticeList.vue"; import StockNoticeList from "./StockNoticeList.vue";
import LongTigerRankList from "./LongTigerRankList.vue"; import LongTigerRankList from "./LongTigerRankList.vue";
import IndustryResearchReportList from "./IndustryResearchReportList.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 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');
@ -557,8 +563,41 @@ function ReFlesh(source) {
<n-tab-pane name="行业研究" tab="行业研究 "> <n-tab-pane name="行业研究" tab="行业研究 ">
<IndustryResearchReportList/> <IndustryResearchReportList/>
</n-tab-pane> </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;"

View File

@ -2,28 +2,31 @@
import {computed, h, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue' import {computed, h, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue'
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { import {
AddGroup,
AddStockGroup,
Follow, Follow,
GetAIResponseResult, GetAIResponseResult,
GetConfig, GetConfig,
GetFollowList, GetFollowList,
GetGroupList,
GetPromptTemplates,
GetStockKLine,
GetStockList, GetStockList,
GetStockMinutePriceLineData,
GetVersionInfo, GetVersionInfo,
Greet, Greet,
NewChatStream, NewChatStream,
RemoveGroup,
RemoveStockGroup,
SaveAIResponseResult, SaveAIResponseResult,
SaveAsMarkdown,
SendDingDingMessageByType, SendDingDingMessageByType,
SetAlarmChangePercent, SetAlarmChangePercent,
SetCostPriceAndVolume, SetCostPriceAndVolume,
SetStockSort,
UnFollow,
ShareAnalysis,
SaveAsMarkdown,
GetPromptTemplates,
SetStockAICron, SetStockAICron,
AddGroup, SetStockSort,
GetGroupList, ShareAnalysis,
AddStockGroup, UnFollow
RemoveStockGroup, RemoveGroup, GetStockKLine, GetStockMinutePriceLineData
} from '../../wailsjs/go/main/App' } from '../../wailsjs/go/main/App'
import { import {
NAvatar, NAvatar,
@ -35,7 +38,6 @@ import {
NText, NText,
useDialog, useDialog,
useMessage, useMessage,
useModal,
useNotification useNotification
} from 'naive-ui' } from 'naive-ui'
import { import {
@ -46,11 +48,8 @@ import {
WindowReload, WindowReload,
WindowUnfullscreen WindowUnfullscreen
} from '../../wailsjs/runtime' } from '../../wailsjs/runtime'
import { import {Add, ChatboxOutline,} from '@vicons/ionicons5'
Add, import {MdEditor, MdPreview} from 'md-editor-v3';
ChatboxOutline,
} from '@vicons/ionicons5'
import {MdPreview,MdEditor } from 'md-editor-v3';
// preview.cssstyle.css // preview.cssstyle.css
//import 'md-editor-v3/lib/preview.css'; //import 'md-editor-v3/lib/preview.css';
import 'md-editor-v3/lib/style.css'; import 'md-editor-v3/lib/style.css';
@ -61,9 +60,10 @@ import html2canvas from "html2canvas";
import {asBlob} from 'html-docx-js-typescript'; import {asBlob} from 'html-docx-js-typescript';
import vueDanmaku from 'vue3-danmaku' import vueDanmaku from 'vue3-danmaku'
import {keys, pad, padStart} from "lodash"; import {keys, padStart} from "lodash";
import {useRoute, useRouter} from 'vue-router' import {useRoute, useRouter} from 'vue-router'
import MoneyTrend from "./moneyTrend.vue"; import MoneyTrend from "./moneyTrend.vue";
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -139,7 +139,6 @@ const feishiInterval= ref(null)
const currentGroupId = ref(0) const currentGroupId = ref(0)
const theme = computed(() => { const theme = computed(() => {
return data.darkTheme ? 'dark' : 'light' return data.darkTheme ? 'dark' : 'light'
}) })
@ -151,9 +150,7 @@ const danmakuColor = computed(()=> {
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 sortedResults = computed(() => { const sortedResults = computed(() => {
////console.log("computed",sortedResults.value)
const sortedKeys = keys(results.value).sort(); const sortedKeys = keys(results.value).sort();
////console.log("sortedKeys",sortedKeys)
const sortedObject = {}; const sortedObject = {};
sortedKeys.forEach(key => { sortedKeys.forEach(key => {
sortedObject[key] = results.value[key]; sortedObject[key] = results.value[key];
@ -215,14 +212,6 @@ onBeforeMount(()=>{
onMounted(() => { onMounted(() => {
message.loading("Loading...") 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 => { GetFollowList(currentGroupId.value).then(result => {
@ -232,7 +221,6 @@ onMounted(() => {
followedStock.StockCode = "gb_" + followedStock.StockCode.replace("us", "").toLowerCase() followedStock.StockCode = "gb_" + followedStock.StockCode.replace("us", "").toLowerCase()
} }
if (!stocks.value.includes(followedStock.StockCode)) { if (!stocks.value.includes(followedStock.StockCode)) {
////console.log("followList",followedStock.StockCode)
stocks.value.push(followedStock.StockCode) stocks.value.push(followedStock.StockCode)
} }
} }
@ -314,17 +302,6 @@ EventsOn("stock_price",(data)=>{
EventsOn("refreshFollowList", (data) => { EventsOn("refreshFollowList", (data) => {
WindowReload() 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) => {
@ -457,6 +434,9 @@ function AddStock(){
if (!stocks.value.includes(data.code)) { if (!stocks.value.includes(data.code)) {
Follow(data.code).then(result => { Follow(data.code).then(result => {
if (result === "关注成功") { if (result === "关注成功") {
if (data.code.startsWith("us")) {
data.code= "gb_" + data.code.replace("us", "").toLowerCase()
}
stocks.value.push(data.code) stocks.value.push(data.code)
message.success(result) message.success(result)
GetFollowList(currentGroupId.value).then(result => { GetFollowList(currentGroupId.value).then(result => {
@ -473,7 +453,6 @@ function AddStock(){
} }
function removeMonitor(code, name, key) { function removeMonitor(code, name, key) {
//console.log("removeMonitor",name,code,key) //console.log("removeMonitor",name,code,key)
stocks.value.splice(stocks.value.indexOf(code), 1) stocks.value.splice(stocks.value.indexOf(code), 1)
@ -499,7 +478,6 @@ function SendDanmu(){
function getStockList(value) { function getStockList(value) {
// //console.log("getStockList",value) // //console.log("getStockList",value)
let result; let result;
result = stockList.value.filter(item => item.name.includes(value) || item.ts_code.includes(value)) result = stockList.value.filter(item => item.name.includes(value) || item.ts_code.includes(value))
@ -524,8 +502,6 @@ function getStockList(value){
} }
} }
function blinkBorder(findId) { function blinkBorder(findId) {
@ -587,13 +563,16 @@ async function updateData(result) {
} }
// result.key=result.sort // result.key=result.sort
results.value = Object.fromEntries(
Object.entries(results.value).filter(
([key]) => !key.includes(result["股票代码"])
));
result.key = GetSortKey(result.sort, result["股票代码"]) result.key = GetSortKey(result.sort, result["股票代码"])
results.value[GetSortKey(result.sort,result["股票代码"])]=result results.value[result.key] = result
if (!stocks.value.includes(result["股票代码"])) { if (!stocks.value.includes(result["股票代码"])) {
delete results.value[result.key] delete results.value[result.key]
} }
////console.log("updateData",result)
} }
@ -602,7 +581,7 @@ async function monitor() {
showPopover.value = true showPopover.value = true
} }
for (let code of stocks.value) { for (let code of stocks.value) {
// //console.log(code)
Greet(code).then(result => { Greet(code).then(result => {
updateData(result) updateData(result)
}) })
@ -612,7 +591,6 @@ async function monitor() {
function GetSortKey(sort, code) { function GetSortKey(sort, code) {
let sortKey = padStart(sort, 8, '0') + "_" + code let sortKey = padStart(sort, 8, '0') + "_" + code
////console.log("GetSortKey:",sortKey)
return sortKey return sortKey
} }
@ -628,16 +606,31 @@ function onSelect(item) {
} }
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) { function search(code, name) {
setTimeout(() => { setTimeout(() => {
//window.open("https://xueqiu.com/S/"+code) //window.open("https://xueqiu.com/S/"+code)
//window.open("https://www.cls.cn/stock?code="+code) //window.open("https://www.cls.cn/stock?code="+code)
//window.open("https://quote.eastmoney.com/"+code+".html") //window.open("https://quote.eastmoney.com/"+code+".html")
//window.open("https://finance.sina.com.cn/realstock/company/"+code+"/nc.shtml") //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) //window.open("https://www.iwencai.com/chat/?question="+code)
openCenteredWindow("https://www.iwencai.com/unifiedwap/result?w=" + name,1000,800)
}, 500) }, 500)
} }
function setStock(code, name) { function setStock(code, name) {
let res = followList.value.filter(item => item.StockCode === code) let res = followList.value.filter(item => item.StockCode === code)
////console.log("res:",res) ////console.log("res:",res)
@ -651,10 +644,12 @@ function setStock(code,name){
formModel.value.cron = res[0].Cron formModel.value.cron = res[0].Cron
modalShow.value = true modalShow.value = true
} }
function clearFeishi() { function clearFeishi() {
//console.log("clearFeishi") //console.log("clearFeishi")
clearInterval(feishiInterval.value) clearInterval(feishiInterval.value)
} }
function showFsChart(code, name) { function showFsChart(code, name) {
data.name = name data.name = name
data.code = code data.code = code
@ -921,6 +916,7 @@ function calculateMA(dayCount,values) {
} }
return result; return result;
} }
function handleKLine() { function handleKLine() {
GetStockKLine(data.code, data.name, 365).then(result => { GetStockKLine(data.code, data.name, 365).then(result => {
//console.log("GetStockKLine",result) //console.log("GetStockKLine",result)
@ -1246,6 +1242,7 @@ function handleKLine(){
}); });
}) })
} }
function showMoney(code, name) { function showMoney(code, name) {
data.code = code data.code = code
data.name = name data.name = name
@ -1268,10 +1265,7 @@ function showK(code,name){
} }
function updateCostPriceAndVolumeNew(code, price, volume, alarm, formModel) { function updateCostPriceAndVolumeNew(code, price, volume, alarm, formModel) {
if (formModel.sort) { if (formModel.sort) {
SetStockSort(formModel.sort, code).then(result => { SetStockSort(formModel.sort, code).then(result => {
//message.success(result) //message.success(result)
@ -1293,6 +1287,7 @@ function updateCostPriceAndVolumeNew(code,price,volume,alarm,formModel){
message.success(result) message.success(result)
GetFollowList(currentGroupId.value).then(result => { GetFollowList(currentGroupId.value).then(result => {
followList.value = result followList.value = result
stocks.value = []
for (const followedStock of result) { for (const followedStock of result) {
if (!stocks.value.includes(followedStock.StockCode)) { if (!stocks.value.includes(followedStock.StockCode)) {
stocks.value.push(followedStock.StockCode) stocks.value.push(followedStock.StockCode)
@ -1344,6 +1339,7 @@ function SendMessage(result,type){
// SendDingDingMessage(msg,result[""]) // SendDingDingMessage(msg,result[""])
SendDingDingMessageByType(msg, result["股票代码"], type) SendDingDingMessageByType(msg, result["股票代码"], type)
} }
function aiReCheckStock(stock, stockCode) { function aiReCheckStock(stock, stockCode) {
data.modelName = "" data.modelName = ""
data.airesult = "" data.airesult = ""
@ -1360,6 +1356,7 @@ function aiReCheckStock(stock,stockCode) {
//message.info("sysPromptId:"+data.sysPromptId) //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 => { GetAIResponseResult(stockCode).then(result => {
if (result.content) { if (result.content) {
@ -1397,8 +1394,7 @@ function aiCheckStock(stock,stockCode){
} }
function getTypeName(type) { function getTypeName(type) {
switch (type) switch (type) {
{
case 1: case 1:
return "涨跌报警" return "涨跌报警"
case 2: case 2:
@ -1461,11 +1457,13 @@ async function copyToClipboard() {
message.error('复制失败: ' + err); message.error('复制失败: ' + err);
} }
} }
function saveAsMarkdown() { function saveAsMarkdown() {
SaveAsMarkdown(data.code, data.name).then(result => { SaveAsMarkdown(data.code, data.name).then(result => {
message.success(result) message.success(result)
}) })
} }
function saveAsMarkdown_old() { 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'); const link = document.createElement('a');
@ -1475,6 +1473,7 @@ function saveAsMarkdown_old() {
URL.revokeObjectURL(link.href); URL.revokeObjectURL(link.href);
link.remove() link.remove()
} }
function getHtml(ref) { function getHtml(ref) {
if (ref.value) { if (ref.value) {
// MdPreview // MdPreview
@ -1539,14 +1538,17 @@ function share(code,name){
}) })
}) })
} }
const addTabModel = ref({ const addTabModel = ref({
name: '', name: '',
sort: 1, sort: 1,
}) })
const addTabPane = ref(false) const addTabPane = ref(false)
function addTab() { function addTab() {
addTabPane.value = true addTabPane.value = true
} }
function saveTabPane() { function saveTabPane() {
AddGroup(addTabModel.value).then(result => { AddGroup(addTabModel.value).then(result => {
message.info(result) message.info(result)
@ -1556,6 +1558,7 @@ function saveTabPane(){
}) })
}) })
} }
function AddStockGroupInfo(groupId, code, name) { function AddStockGroupInfo(groupId, code, name) {
if (code.startsWith("gb_")) { if (code.startsWith("gb_")) {
code = "us" + code.replace("gb_", "").toLowerCase() code = "us" + code.replace("gb_", "").toLowerCase()
@ -1568,11 +1571,11 @@ function AddStockGroupInfo(groupId,code,name){
}) })
} }
function updateTab(name) { function updateTab(name) {
currentGroupId.value = Number(name) currentGroupId.value = Number(name)
GetFollowList(currentGroupId.value).then(result => { GetFollowList(currentGroupId.value).then(result => {
stocks.value = [] stocks.value = []
//console.log("GetFollowList",result)
followList.value = result followList.value = result
for (const followedStock of result) { for (const followedStock of result) {
if (followedStock.StockCode.startsWith("us")) { if (followedStock.StockCode.startsWith("us")) {
@ -1585,6 +1588,7 @@ function updateTab(name){
message.destroyAll() message.destroyAll()
}) })
} }
function delTab(name) { function delTab(name) {
let infos = groupList.value = groupList.value.filter(item => item.ID === Number(name)) let infos = groupList.value = groupList.value.filter(item => item.ID === Number(name))
dialog.create({ dialog.create({
@ -1603,6 +1607,7 @@ function delTab(name){
} }
}) })
} }
function delStockGroup(code, name, groupId) { function delStockGroup(code, name, groupId) {
RemoveStockGroup(code, name, groupId).then(result => { RemoveStockGroup(code, name, groupId).then(result => {
updateTab(groupId) updateTab(groupId)
@ -1619,6 +1624,7 @@ function searchNotice(stockCode){
}, },
}) })
} }
function searchStockReport(stockCode) { function searchStockReport(stockCode) {
router.push({ router.push({
name: 'market', name: 'market',
@ -1631,26 +1637,35 @@ function searchStockReport(stockCode){
</script> </script>
<template> <template>
<vue-danmaku v-model:danmus="danmus" useSlot style="height:100px; width:100%;z-index: 9;position:absolute; top: 400px; pointer-events: none;" > <vue-danmaku v-model:danmus="danmus" useSlot
style="height:100px; width:100%;z-index: 9;position:absolute; top: 400px; pointer-events: none;">
<template v-slot:dm="{ index, danmu }"> <template v-slot:dm="{ index, danmu }">
<n-gradient-text type="info"> <n-gradient-text type="info">
<n-icon :component="ChatboxOutline"/>{{ danmu }} <n-icon :component="ChatboxOutline"/>
{{ danmu }}
</n-gradient-text> </n-gradient-text>
</template> </template>
</vue-danmaku> </vue-danmaku>
<n-tabs type="card" style="--wails-draggable:drag" animated addable :data-currentGroupId="currentGroupId" :value="currentGroupId" @add="addTab" @update-value="updateTab" placement="top" @close="(key)=>{delTab(key)}"> <n-tabs type="card" style="--wails-draggable:drag" animated addable :data-currentGroupId="currentGroupId"
:value="currentGroupId" @add="addTab" @update-value="updateTab" placement="top" @close="(key)=>{delTab(key)}">
<n-tab-pane :name="0" :tab="'全部'"> <n-tab-pane :name="0" :tab="'全部'">
<n-grid :x-gap="8" :cols="3" :y-gap="8"> <n-grid :x-gap="8" :cols="3" :y-gap="8">
<n-gi :id="result['股票代码']+'_gi'" v-for="result in sortedResults" style="margin-left: 2px;"> <n-gi :id="result['股票代码']+'_gi'" v-for="result in sortedResults" style="margin-left: 2px;">
<n-card :data-sort="result.sort" :id="result['股票代码']" :data-code="result['股票代码']" :bordered="true" :title="result['股票名称']" :closable="false" @close="removeMonitor(result['股票代码'],result['股票名称'],result.key)"> <n-card :data-sort="result.sort" :id="result['股票代码']" :data-code="result['股票代码']" :bordered="true"
:title="result['股票名称']" :closable="false"
@close="removeMonitor(result['股票代码'],result['股票名称'],result.key)">
<n-grid :cols="1" :y-gap="6"> <n-grid :cols="1" :y-gap="6">
<n-gi> <n-gi>
<n-text :type="result.type"> <n-text :type="result.type">
<n-number-animation :duration="1000" :precision="2" :from="result['上次当前价格']" :to="Number(result['当前价格'])" /> <n-number-animation :duration="1000" :precision="2" :from="result['上次当前价格']"
<n-tag size="small" :type="result.type" :bordered="false" v-if="result['盘前盘后']>0">({{result['盘前盘后']}} {{result['盘前盘后涨跌幅']}}%)</n-tag> :to="Number(result['当前价格'])"/>
<n-tag size="small" :type="result.type" :bordered="false" v-if="result['盘前盘后']>0">
({{ result['盘前盘后'] }} {{ result['盘前盘后涨跌幅'] }}%)
</n-tag>
</n-text> </n-text>
<n-text style="padding-left: 10px;" :type="result.type"> <n-text style="padding-left: 10px;" :type="result.type">
<n-number-animation :duration="1000" :precision="3" :from="0" :to="result.changePercent" />% <n-number-animation :duration="1000" :precision="3" :from="0" :to="result.changePercent"/>
%
</n-text>&nbsp; </n-text>&nbsp;
<n-text size="small" v-if="result.costVolume>0" :type="result.type"> <n-text size="small" v-if="result.costVolume>0" :type="result.type">
<n-number-animation :duration="1000" :precision="2" :from="0" :to="result.profitAmountToday"/> <n-number-animation :duration="1000" :precision="2" :from="0" :to="result.profitAmountToday"/>
@ -1720,10 +1735,12 @@ function searchStockReport(stockCode){
<template #header-extra> <template #header-extra>
<n-tag size="small" :bordered="false">{{ result['股票代码'] }}</n-tag>&nbsp; <n-tag size="small" :bordered="false">{{ result['股票代码'] }}</n-tag>&nbsp;
<n-button size="tiny" secondary type="primary" @click="removeMonitor(result['股票代码'],result['股票名称'],result.key)"> <n-button size="tiny" secondary type="primary"
@click="removeMonitor(result['股票代码'],result['股票名称'],result.key)">
取消关注 取消关注
</n-button>&nbsp; </n-button>&nbsp;
<n-button size="tiny" v-if="data.openAiEnable" secondary type="warning" @click="aiCheckStock(result['股票名称'],result['股票代码'])"> <n-button size="tiny" v-if="data.openAiEnable" secondary type="warning"
@click="aiCheckStock(result['股票名称'],result['股票代码'])">
AI分析 AI分析
</n-button> </n-button>
@ -1732,20 +1749,33 @@ function searchStockReport(stockCode){
<n-flex justify="center"> <n-flex justify="center">
<n-text :type="'info'">{{ result["日期"] + " " + result["时间"] }}</n-text> <n-text :type="'info'">{{ result["日期"] + " " + result["时间"] }}</n-text>
<n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{ result.volume + "股" }}</n-tag> <n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{ result.volume + "股" }}</n-tag>
<n-tag size="small" v-if="result.costPrice>0" :type="result.profitType">{{"成本:"+result.costPrice+"*"+result.costVolume+" "+result.profit+"%"+" ( "+result.profitAmount+" ¥ )"}}</n-tag> <n-tag size="small" v-if="result.costPrice>0" :type="result.profitType">
{{ "成本:" + result.costPrice + "*" + result.costVolume + " " + result.profit + "%" + " ( " + result.profitAmount + " ¥ )" }}
</n-tag>
</n-flex> </n-flex>
</template> </template>
<template #action> <template #action>
<n-flex justify="left"> <n-flex justify="left">
<n-button size="tiny" type="warning" @click="setStock(result['股票代码'],result['股票名称'])"> 成本 </n-button> <n-button size="tiny" type="warning" @click="setStock(result['股票代码'],result['股票名称'])"> 成本
<n-button size="tiny" type="error" @click="showFenshi(result['股票代码'],result['股票名称'],result.changePercent)"> 分时 </n-button> </n-button>
<n-button size="tiny" type="error"
@click="showFenshi(result['股票代码'],result['股票名称'],result.changePercent)"> 分时
</n-button>
<n-button size="tiny" type="error" @click="showK(result['股票代码'],result['股票名称'])"> 日K</n-button> <n-button size="tiny" type="error" @click="showK(result['股票代码'],result['股票名称'])"> 日K</n-button>
<n-button size="tiny" type="error" v-if="result['买一报价']>0" @click="showMoney(result['股票代码'],result['股票名称'])"> 资金 </n-button> <n-button size="tiny" type="error" v-if="result['买一报价']>0"
<n-button size="tiny" type="success" @click="search(result['股票代码'],result['股票名称'])"> 详情 </n-button> @click="showMoney(result['股票代码'],result['股票名称'])"> 资金
<n-button v-if="result['买一报价']>0" size="tiny" type="success" @click="searchNotice(result['股票代码'])"> 公告 </n-button> </n-button>
<n-button v-if="result['买一报价']>0" size="tiny" type="success" @click="searchStockReport(result['股票代码'])"> 研报 </n-button> <n-button size="tiny" type="success" @click="search(result['股票代码'],result['股票名称'])"> 详情
</n-button>
<n-button v-if="result['买一报价']>0" size="tiny" type="success"
@click="searchNotice(result['股票代码'])"> 公告
</n-button>
<n-button v-if="result['买一报价']>0" size="tiny" type="success"
@click="searchStockReport(result['股票代码'])"> 研报
</n-button>
<n-flex justify="right"> <n-flex justify="right">
<n-dropdown trigger="click" :options="groupList" key-field="ID" label-field="name" @select="(groupId) => AddStockGroupInfo(groupId,result['股票代码'],result['股票名称'])"> <n-dropdown trigger="click" :options="groupList" key-field="ID" label-field="name"
@select="(groupId) => AddStockGroupInfo(groupId,result['股票代码'],result['股票名称'])">
<n-button type="warning" size="tiny">设置分组</n-button> <n-button type="warning" size="tiny">设置分组</n-button>
</n-dropdown> </n-dropdown>
</n-flex> </n-flex>
@ -1758,15 +1788,21 @@ function searchStockReport(stockCode){
<n-tab-pane closable v-for="group in groupList" :group-id="group.ID" :name="group.ID" :tab="group.name"> <n-tab-pane closable v-for="group in groupList" :group-id="group.ID" :name="group.ID" :tab="group.name">
<n-grid :x-gap="8" :cols="3" :y-gap="8"> <n-grid :x-gap="8" :cols="3" :y-gap="8">
<n-gi :id="result['股票代码']+'_gi'" v-for="result in groupResults" style="margin-left: 2px;"> <n-gi :id="result['股票代码']+'_gi'" v-for="result in groupResults" style="margin-left: 2px;">
<n-card :data-sort="result.sort" :id="result['股票代码']" :data-code="result['股票代码']" :bordered="true" :title="result['股票名称']" :closable="false" @close="removeMonitor(result['股票代码'],result['股票名称'],result.key)"> <n-card :data-sort="result.sort" :id="result['股票代码']" :data-code="result['股票代码']" :bordered="true"
:title="result['股票名称']" :closable="false"
@close="removeMonitor(result['股票代码'],result['股票名称'],result.key)">
<n-grid :cols="1" :y-gap="6"> <n-grid :cols="1" :y-gap="6">
<n-gi> <n-gi>
<n-text :type="result.type"> <n-text :type="result.type">
<n-number-animation :duration="1000" :precision="2" :from="result['上次当前价格']" :to="Number(result['当前价格'])" /> <n-number-animation :duration="1000" :precision="2" :from="result['上次当前价格']"
<n-tag size="small" :type="result.type" :bordered="false" v-if="result['盘前盘后']>0">({{result['盘前盘后']}} {{result['盘前盘后涨跌幅']}}%)</n-tag> :to="Number(result['当前价格'])"/>
<n-tag size="small" :type="result.type" :bordered="false" v-if="result['盘前盘后']>0">
({{ result['盘前盘后'] }} {{ result['盘前盘后涨跌幅'] }}%)
</n-tag>
</n-text> </n-text>
<n-text style="padding-left: 10px;" :type="result.type"> <n-text style="padding-left: 10px;" :type="result.type">
<n-number-animation :duration="1000" :precision="3" :from="0" :to="result.changePercent" />% <n-number-animation :duration="1000" :precision="3" :from="0" :to="result.changePercent"/>
%
</n-text>&nbsp; </n-text>&nbsp;
<n-text size="small" v-if="result.costVolume>0" :type="result.type"> <n-text size="small" v-if="result.costVolume>0" :type="result.type">
<n-number-animation :duration="1000" :precision="2" :from="0" :to="result.profitAmountToday"/> <n-number-animation :duration="1000" :precision="2" :from="0" :to="result.profitAmountToday"/>
@ -1836,32 +1872,49 @@ function searchStockReport(stockCode){
<template #header-extra> <template #header-extra>
<n-tag size="small" :bordered="false">{{ result['股票代码'] }}</n-tag>&nbsp; <n-tag size="small" :bordered="false">{{ result['股票代码'] }}</n-tag>&nbsp;
<n-button size="tiny" secondary type="primary" @click="removeMonitor(result['股票代码'],result['股票名称'],result.key)"> <n-button size="tiny" secondary type="primary"
@click="removeMonitor(result['股票代码'],result['股票名称'],result.key)">
取消关注 取消关注
</n-button>&nbsp; </n-button>&nbsp;
<n-button size="tiny" v-if="data.openAiEnable" secondary type="warning" @click="aiCheckStock(result['股票名称'],result['股票代码'])"> <n-button size="tiny" v-if="data.openAiEnable" secondary type="warning"
@click="aiCheckStock(result['股票名称'],result['股票代码'])">
AI分析 AI分析
</n-button>&nbsp; </n-button>&nbsp;
<n-button secondary type="error" size="tiny" @click="delStockGroup(result['股票代码'],result['股票名称'],group.ID)">移出分组</n-button> <n-button secondary type="error" size="tiny"
@click="delStockGroup(result['股票代码'],result['股票名称'],group.ID)">移出分组
</n-button>
</template> </template>
<template #footer> <template #footer>
<n-flex justify="center"> <n-flex justify="center">
<n-text :type="'info'">{{ result["日期"] + " " + result["时间"] }}</n-text> <n-text :type="'info'">{{ result["日期"] + " " + result["时间"] }}</n-text>
<n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{ result.volume + "股" }}</n-tag> <n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{ result.volume + "股" }}</n-tag>
<n-tag size="small" v-if="result.costPrice>0" :type="result.profitType">{{"成本:"+result.costPrice+"*"+result.costVolume+" "+result.profit+"%"+" ( "+result.profitAmount+" ¥ )"}}</n-tag> <n-tag size="small" v-if="result.costPrice>0" :type="result.profitType">
{{ "成本:" + result.costPrice + "*" + result.costVolume + " " + result.profit + "%" + " ( " + result.profitAmount + " ¥ )" }}
</n-tag>
</n-flex> </n-flex>
</template> </template>
<template #action> <template #action>
<n-flex justify="left"> <n-flex justify="left">
<n-button size="tiny" type="warning" @click="setStock(result['股票代码'],result['股票名称'])"> 成本 </n-button> <n-button size="tiny" type="warning" @click="setStock(result['股票代码'],result['股票名称'])"> 成本
<n-button size="tiny" type="error" @click="showFenshi(result['股票代码'],result['股票名称'],result.changePercent)"> 分时 </n-button> </n-button>
<n-button size="tiny" type="error"
@click="showFenshi(result['股票代码'],result['股票名称'],result.changePercent)"> 分时
</n-button>
<n-button size="tiny" type="error" @click="showK(result['股票代码'],result['股票名称'])"> 日K</n-button> <n-button size="tiny" type="error" @click="showK(result['股票代码'],result['股票名称'])"> 日K</n-button>
<n-button size="tiny" type="error" v-if="result['买一报价']>0" @click="showMoney(result['股票代码'],result['股票名称'])"> 资金 </n-button> <n-button size="tiny" type="error" v-if="result['买一报价']>0"
<n-button size="tiny" type="success" @click="search(result['股票代码'],result['股票名称'])"> 详情 </n-button> @click="showMoney(result['股票代码'],result['股票名称'])"> 资金
<n-button v-if="result['买一报价']>0" size="tiny" type="success" @click="searchNotice(result['股票代码'])"> 公告 </n-button> </n-button>
<n-button v-if="result['买一报价']>0" size="tiny" type="success" @click="searchStockReport(result['股票代码'])"> 研报 </n-button> <n-button size="tiny" type="success" @click="search(result['股票代码'],result['股票名称'])"> 详情
</n-button>
<n-button v-if="result['买一报价']>0" size="tiny" type="success"
@click="searchNotice(result['股票代码'])"> 公告
</n-button>
<n-button v-if="result['买一报价']>0" size="tiny" type="success"
@click="searchStockReport(result['股票代码'])"> 研报
</n-button>
<n-flex justify="right"> <n-flex justify="right">
<n-dropdown trigger="click" :options="groupList" key-field="ID" label-field="name" @select="(groupId) => AddStockGroupInfo(groupId,result['股票代码'],result['股票名称'])"> <n-dropdown trigger="click" :options="groupList" key-field="ID" label-field="name"
@select="(groupId) => AddStockGroupInfo(groupId,result['股票代码'],result['股票名称'])">
<n-button type="warning" size="tiny">设置分组</n-button> <n-button type="warning" size="tiny">设置分组</n-button>
</n-dropdown> </n-dropdown>
</n-flex> </n-flex>
@ -1900,7 +1953,8 @@ function searchStockReport(stockCode){
</n-input-group> </n-input-group>
<!-- </n-card>--> <!-- </n-card>-->
</div> </div>
<n-modal transform-origin="center" size="small" v-model:show="modalShow" :title="formModel.name" style="width: 400px" :preset="'card'"> <n-modal transform-origin="center" size="small" v-model:show="modalShow" :title="formModel.name" style="width: 400px"
:preset="'card'">
<n-form :model="formModel" :rules="{ <n-form :model="formModel" :rules="{
costPrice: { required: true, message: '请输入成本'}, costPrice: { required: true, message: '请输入成本'},
volume: { required: true, message: '请输入数量'}, volume: { required: true, message: '请输入数量'},
@ -1945,7 +1999,10 @@ function searchStockReport(stockCode){
</n-form-item> </n-form-item>
</n-form> </n-form>
<template #footer> <template #footer>
<n-button type="primary" @click="updateCostPriceAndVolumeNew(formModel.code,formModel.costPrice,formModel.volume,formModel.alarm,formModel)">保存</n-button> <n-button type="primary"
@click="updateCostPriceAndVolumeNew(formModel.code,formModel.costPrice,formModel.volume,formModel.alarm,formModel)">
保存
</n-button>
</template> </template>
</n-modal> </n-modal>
@ -1954,12 +2011,14 @@ function searchStockReport(stockCode){
:model="addTabModel" :model="addTabModel"
size="medium" size="medium"
label-placement="left" label-placement="left"
> <n-grid :cols="2" > >
<n-grid :cols="2">
<n-form-item-gi label="分组名称:" path="name" :span="5"> <n-form-item-gi label="分组名称:" path="name" :span="5">
<n-input v-model:value="addTabModel.name" style="width: 100%" placeholder="请输入分组名称"/> <n-input v-model:value="addTabModel.name" style="width: 100%" placeholder="请输入分组名称"/>
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi label="分组排序:" path="sort" :span="5"> <n-form-item-gi label="分组排序:" path="sort" :span="5">
<n-input-number v-model:value="addTabModel.sort" style="width: 100%" min="0" placeholder="请输入分组排序值" ></n-input-number> <n-input-number v-model:value="addTabModel.sort" style="width: 100%" min="0"
placeholder="请输入分组排序值"></n-input-number>
</n-form-item-gi> </n-form-item-gi>
</n-grid> </n-grid>
</n-form> </n-form>
@ -1974,28 +2033,36 @@ function searchStockReport(stockCode){
</n-flex> </n-flex>
</template> </template>
</n-modal> </n-modal>
<n-modal v-model:show="modalShow2" :title="data.name+' '+ data.changePercent+'%'" style="width: 1000px" :preset="'card'" @after-enter="handleFeishi" @after-leave="clearFeishi"> <n-modal v-model:show="modalShow2" :title="data.name+' '+ data.changePercent+'%'" style="width: 1000px"
:preset="'card'" @after-enter="handleFeishi" @after-leave="clearFeishi">
<!-- <n-image :src="data.fenshiURL" />--> <!-- <n-image :src="data.fenshiURL" />-->
<div ref="kLineChartRef2" style="width: 1000px; height: 500px;"></div> <div ref="kLineChartRef2" style="width: 1000px; height: 500px;"></div>
</n-modal> </n-modal>
<n-modal v-model:show="modalShow3" :title="data.name" style="width: 1000px" :preset="'card'" @after-enter="handleKLine"> <n-modal v-model:show="modalShow3" :title="data.name" style="width: 1000px" :preset="'card'"
@after-enter="handleKLine">
<!-- <n-image :src="data.kURL" />--> <!-- <n-image :src="data.kURL" />-->
<div ref="kLineChartRef" style="width: 1000px; height: 500px;"></div> <div ref="kLineChartRef" style="width: 1000px; height: 500px;"></div>
</n-modal> </n-modal>
<n-modal transform-origin="center" v-model:show="modalShow4" preset="card" style="width: 800px;" :title="'['+data.name+']AI分析结果'" > <n-modal transform-origin="center" v-model:show="modalShow4" preset="card" style="width: 800px;"
:title="'['+data.name+']AI分析结果'">
<n-spin size="small" :show="data.loading"> <n-spin size="small" :show="data.loading">
<MdEditor v-if="enableEditor" :toolbars="toolbars" ref="mdEditorRef" style="height: 440px;text-align: left" :modelValue="data.airesult" :theme="theme"> <MdEditor v-if="enableEditor" :toolbars="toolbars" ref="mdEditorRef" style="height: 440px;text-align: left"
:modelValue="data.airesult" :theme="theme">
<template #defToolbars> <template #defToolbars>
<ExportPDF :file-name="data.name+'['+data.code+']AI分析报告'" style="text-align: left" :modelValue="data.airesult" @onProgress="handleProgress" /> <ExportPDF :file-name="data.name+'['+data.code+']AI分析报告'" style="text-align: left"
:modelValue="data.airesult" @onProgress="handleProgress"/>
</template> </template>
</MdEditor> </MdEditor>
<MdPreview v-if="!enableEditor" ref="mdPreviewRef" style="height: 440px;text-align: left" :modelValue="data.airesult" :theme="theme"/> <MdPreview v-if="!enableEditor" ref="mdPreviewRef" style="height: 440px;text-align: left"
:modelValue="data.airesult" :theme="theme"/>
</n-spin> </n-spin>
<template #footer> <template #footer>
<n-flex justify="space-between" ref="tipsRef"> <n-flex justify="space-between" ref="tipsRef">
<n-text type="info" v-if="data.time"> <n-text type="info" v-if="data.time">
<n-tag v-if="data.modelName" type="warning" round :title="data.chatId" :bordered="false">{{data.modelName}}</n-tag> <n-tag v-if="data.modelName" type="warning" round :title="data.chatId" :bordered="false">
{{ data.modelName }}
</n-tag>
{{ data.time }} {{ data.time }}
</n-text> </n-text>
<n-text type="error">*AI分析结果仅供参考请以实际行情为准投资需谨慎风险自担</n-text> <n-text type="error">*AI分析结果仅供参考请以实际行情为准投资需谨慎风险自担</n-text>
@ -2004,8 +2071,10 @@ function searchStockReport(stockCode){
<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="data.sysPromptId" label-field="name" value-field="ID" :options="sysPromptOptions" placeholder="请选择系统提示词" /> <n-select style="width: 49%" v-model:value="data.sysPromptId" label-field="name" value-field="ID"
<n-select style="width: 49%" v-model:value="data.question" label-field="name" value-field="content" :options="userPromptOptions" placeholder="请选择用户提示词" /> :options="sysPromptOptions" placeholder="请选择系统提示词"/>
<n-select style="width: 49%" v-model:value="data.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="data.question" style="text-align: left" clearable <n-input v-model:value="data.question" style="text-align: left" clearable
@ -2028,7 +2097,8 @@ function searchStockReport(stockCode){
</template> </template>
</n-modal> </n-modal>
<n-modal v-model:show="modalShow5" :title="data.name+'资金趋势'" style="width: 1000px" :preset="'card'"> <n-modal v-model:show="modalShow5" :title="data.name+'资金趋势'" style="width: 1000px" :preset="'card'">
<money-trend :code="data.code" :name="data.name" :days="360" :dark-theme="data.darkTheme" :chart-height="500"></money-trend> <money-trend :code="data.code" :name="data.name" :days="360" :dark-theme="data.darkTheme"
:chart-height="500"></money-trend>
</n-modal> </n-modal>
</template> </template>
@ -2040,6 +2110,7 @@ function searchStockReport(stockCode){
.md-editor-preview p { .md-editor-preview p {
text-align: left !important; text-align: left !important;
} }
/* 添加闪烁效果的CSS类 */ /* 添加闪烁效果的CSS类 */
.blink-border { .blink-border {
animation: blink-border 1s linear infinite; animation: blink-border 1s linear infinite;

View File

@ -15,6 +15,8 @@ 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 EMDictCode(arg1:string):Promise<Array<any>>;
@ -65,8 +67,16 @@ 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 IndustryResearchReport(arg1:string):Promise<Array<any>>;
export function InvestCalendarTimeLine(arg1:string):Promise<Array<any>>;
export function LongTigerRank(arg1:string):Promise<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>;
@ -83,6 +93,8 @@ 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>;

View File

@ -26,6 +26,10 @@ 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);
} }
@ -126,10 +130,26 @@ 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) { export function IndustryResearchReport(arg1) {
return window['go']['main']['App']['IndustryResearchReport'](arg1); return window['go']['main']['App']['IndustryResearchReport'](arg1);
} }
export function InvestCalendarTimeLine(arg1) {
return window['go']['main']['App']['InvestCalendarTimeLine'](arg1);
}
export function LongTigerRank(arg1) { export function LongTigerRank(arg1) {
return window['go']['main']['App']['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); 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);
} }