Compare commits

...

5 Commits

Author SHA1 Message Date
ArvinLovegood
c3521c6d7f feat(stock):新增东财用户标识支持并优化新闻抓取逻辑
- 在设置中增加东财唯一标识(qgqpBId)字段,用于搜索股票接口鉴权
- 优化 Telegraph 列表获取逻辑,使用协程并发执行多个数据源请求
- 调整 TradingView 新闻定时任务间隔时间,从60秒减少到10秒
- 修改新闻列表排序规则,优先按数据时间降序排列
- 更新 TradingView 新闻去重条件,由内容匹配改为时间和标题匹配
- 限制 TradingView 新闻详情处理数量,最多只处理前10条
- 完善错误提示信息显示逻辑,兼容不同字段的消息返回
- 删除冗余的 GetLevel 函数,直接在赋值处判断等级逻辑
- 增加测试初始化情感分析模块调用
- 前端表单同步增加 qgqpBId 字段绑定与持久化配置支持
2025-11-26 14:58:12 +08:00
ArvinLovegood
93b37ca621 feat(market):新增外媒新闻功能并优化展示逻辑
- 在定时任务中增加TradingViewNews新闻抓取
- 增加TradingView新闻详情接口及数据解析逻辑
- 修改Telegraph模型字段索引提升查询性能
- 前端新增"外媒"新闻列表展示模块
- 根据HTTP代理配置动态调整新闻栏布局
- 修复时间转换及去重逻辑提高数据准确性
- 调整新闻列表显示样式增强可读性
2025-11-25 17:44:13 +08:00
ArvinLovegood
7069af869b feat(frontend):优化市场分析组件并增强标签处理逻辑
- 添加主要指数(mainIndex),包括中美日韩等地重要城市指数
- 更新图表展示逻辑,使用mainIndex代替原有的china索引
- 引入数字动画效果,提升用户体验
- 在后端增加标签类型过滤,仅处理type为"subject"的标签
- 调整标签词频,通过ReAddToken方法提高关键词权重
2025-11-25 12:23:54 +08:00
ArvinLovegood
dbb6789c05 feat(frontend):市场快讯里面添加全球股指展示功能并优化情绪分析图表
- 引入 GlobalStockIndexes 接口获取全球主要股指数据
- 添加中国主要股指筛选逻辑(上海、深圳、香港等)
- 优化市场情绪图表数值显示精度
- 调整图表布局结构,增强可视化效果
- 修改市场情绪强弱指标名称提升可读性
- 定时获取最新股指数据(每2秒刷新)
- 调整负面金融词汇权重以提高分析准确性
2025-11-24 18:08:10 +08:00
ArvinLovegood
8aed4d2753 feat(dict):优化金融股票分词字典结构与内容
- 删除重复或冗余的词条,如“人工智能”、“云计算”等在多个分类中重复出现的词汇
- 调整并统一章节编号,确保从一至九的连续性和逻辑性
- 移除不再适用的覆盖场景描述,提升字典的专业性与准确性
- 更新权重说明注释,去除不必要的分数细节,保持清晰易懂
- 重新组织词条顺序,使同类项集中,提高检索效率
- 清理负权重词汇列表中的多余条目,强化过滤机制
- 精简A股龙头公司条目,聚焦更广泛的财务与估值指标词条
- 统一格式排版,增强可读性和维护便利性
2025-11-23 20:48:28 +08:00
15 changed files with 282 additions and 66 deletions

22
app.go
View File

@@ -410,6 +410,10 @@ func (a *App) domReady(ctx context.Context) {
//定时更新数据
config := data.GetSettingConfig()
go func() {
go data.NewMarketNewsApi().TelegraphList(30)
go data.NewMarketNewsApi().GetSinaNews(30)
go data.NewMarketNewsApi().TradingViewNews()
interval := config.RefreshInterval
if interval <= 0 {
interval = 1
@@ -453,6 +457,19 @@ func (a *App) domReady(ctx context.Context) {
} else {
a.cronEntrys["newSinaNews"] = entryIDSina
}
entryIDTradingViewNews, err := a.cron.AddFunc(fmt.Sprintf("@every %ds", interval+10), func() {
news := data.NewMarketNewsApi().TradingViewNews()
if config.EnablePushNews {
go a.NewsPush(news)
}
go runtime.EventsEmit(a.ctx, "tradingViewNews", news)
})
if err != nil {
logger.SugaredLogger.Errorf("AddFunc error:%s", err.Error())
} else {
a.cronEntrys["tradingViewNews"] = entryIDTradingViewNews
}
}()
//刷新基金净值信息
@@ -1358,8 +1375,9 @@ func (a *App) GetTelegraphList(source string) *[]*models.Telegraph {
func (a *App) ReFleshTelegraphList(source string) *[]*models.Telegraph {
//data.NewMarketNewsApi().GetNewTelegraph(30)
data.NewMarketNewsApi().TelegraphList(30)
data.NewMarketNewsApi().GetSinaNews(30)
go data.NewMarketNewsApi().TelegraphList(30)
go data.NewMarketNewsApi().GetSinaNews(30)
go data.NewMarketNewsApi().TradingViewNews()
telegraphs := data.NewMarketNewsApi().GetTelegraphList(source)
return telegraphs
}

View File

@@ -3,13 +3,14 @@ package tools
import (
"context"
"encoding/json"
"go-stock/backend/data"
"go-stock/backend/logger"
"strings"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"github.com/duke-git/lancet/v2/random"
"github.com/tidwall/gjson"
"go-stock/backend/data"
"go-stock/backend/logger"
"strings"
)
// @Author spark
@@ -51,7 +52,7 @@ func (q QueryMarketNews) InvokableRun(ctx context.Context, argumentsInJSON strin
})
}
news := data.NewMarketNewsApi().GetNewsList("财联社电报", random.RandInt(100, 500))
news := data.NewMarketNewsApi().GetNewsList("", random.RandInt(100, 500))
messageText := strings.Builder{}
for _, telegraph := range *news {
messageText.WriteString("## " + telegraph.Time + ":" + "\n")

View File

@@ -8,6 +8,7 @@ import (
"go-stock/backend/logger"
"go-stock/backend/models"
"go-stock/backend/util"
"net/url"
"strconv"
"strings"
"time"
@@ -53,7 +54,7 @@ func (m MarketNewsApi) TelegraphList(crawlTimeOut int64) *[]models.Telegraph {
for _, v := range rollData {
news := v.(map[string]any)
ctime, _ := convertor.ToInt(news["ctime"])
dataTime := time.Unix(ctime, 0)
dataTime := time.Unix(ctime, 0).Local()
logger.SugaredLogger.Debugf("dataTime: %s", dataTime)
telegraph := models.Telegraph{
Content: news["content"].(string),
@@ -61,7 +62,7 @@ func (m MarketNewsApi) TelegraphList(crawlTimeOut int64) *[]models.Telegraph {
DataTime: &dataTime,
Url: news["shareurl"].(string),
Source: "财联社电报",
IsRed: GetLevel(news["level"].(string)),
IsRed: (news["level"].(string)) != "C",
SentimentResult: AnalyzeSentiment(news["content"].(string)).Description,
}
cnt := int64(0)
@@ -96,9 +97,6 @@ func (m MarketNewsApi) TelegraphList(crawlTimeOut int64) *[]models.Telegraph {
return &telegraphs
}
func GetLevel(s string) bool {
return s > "C"
}
func (m MarketNewsApi) GetNewTelegraph(crawlTimeOut int64) *[]models.Telegraph {
url := "https://www.cls.cn/telegraph"
@@ -167,9 +165,9 @@ func (m MarketNewsApi) GetNewTelegraph(crawlTimeOut int64) *[]models.Telegraph {
func (m MarketNewsApi) GetNewsList(source string, limit int) *[]*models.Telegraph {
news := &[]*models.Telegraph{}
if source != "" {
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("id desc").Limit(limit).Find(news)
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("data_time desc,time desc").Limit(limit).Find(news)
} else {
db.Dao.Model(news).Preload("TelegraphTags").Order("id desc").Limit(limit).Find(news)
db.Dao.Model(news).Preload("TelegraphTags").Order("data_time desc,time desc").Limit(limit).Find(news)
}
for _, item := range *news {
tags := &[]models.Tags{}
@@ -188,9 +186,9 @@ func (m MarketNewsApi) GetNewsList2(source string, limit int) *[]*models.Telegra
NewMarketNewsApi().TelegraphList(30)
news := &[]*models.Telegraph{}
if source != "" {
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("id desc,is_red desc").Limit(limit).Find(news)
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("data_time desc,is_red desc").Limit(limit).Find(news)
} else {
db.Dao.Model(news).Preload("TelegraphTags").Order("id desc,is_red desc").Limit(limit).Find(news)
db.Dao.Model(news).Preload("TelegraphTags").Order("data_time desc,is_red desc").Limit(limit).Find(news)
}
for _, item := range *news {
tags := &[]models.Tags{}
@@ -209,9 +207,9 @@ func (m MarketNewsApi) GetNewsList2(source string, limit int) *[]*models.Telegra
func (m MarketNewsApi) GetTelegraphList(source string) *[]*models.Telegraph {
news := &[]*models.Telegraph{}
if source != "" {
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("id desc").Limit(20).Find(news)
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("data_time desc,time desc").Limit(50).Find(news)
} else {
db.Dao.Model(news).Preload("TelegraphTags").Order("id desc").Limit(20).Find(news)
db.Dao.Model(news).Preload("TelegraphTags").Order("data_time desc,time desc").Limit(50).Find(news)
}
for _, item := range *news {
tags := &[]models.Tags{}
@@ -268,6 +266,10 @@ func (m MarketNewsApi) GetSinaNews(crawlTimeOut uint) *[]models.Telegraph {
//logger.SugaredLogger.Infof("%s:%s", data["create_time"], data["rich_text"])
telegraph.Content = data["rich_text"].(string)
telegraph.Time = strings.Split(data["create_time"].(string), " ")[1]
dataTime, _ := time.ParseInLocation("2006-01-02 15:04:05", data["create_time"].(string), time.Local)
if &dataTime != nil {
telegraph.DataTime = &dataTime
}
tags := data["tag"].([]any)
telegraph.SubjectTags = lo.Map(tags, func(tagItem any, index int) string {
name := tagItem.(map[string]any)["name"].(string)
@@ -286,7 +288,7 @@ func (m MarketNewsApi) GetSinaNews(crawlTimeOut uint) *[]models.Telegraph {
if telegraph.Content != "" {
telegraph.SentimentResult = AnalyzeSentiment(telegraph.Content).Description
cnt := int64(0)
db.Dao.Model(telegraph).Where("time=? and source=?", telegraph.Time, telegraph.Source).Count(&cnt)
db.Dao.Model(telegraph).Where("content=? and source=?", telegraph.Content, telegraph.Source).Count(&cnt)
if cnt == 0 {
db.Dao.Create(&telegraph)
telegraphs = append(telegraphs, telegraph)
@@ -640,14 +642,16 @@ func (m MarketNewsApi) EMDictCode(code string, cache *freecache.Cache) []any {
return respMap["data"].([]any)
}
func (m MarketNewsApi) TradingViewNews() *[]models.TVNews {
func (m MarketNewsApi) TradingViewNews() *[]models.Telegraph {
client := resty.New()
config := GetSettingConfig()
if config.HttpProxyEnabled && config.HttpProxy != "" {
client.SetProxy(config.HttpProxy)
}
TVNews := &[]models.TVNews{}
url := "https://news-mediator.tradingview.com/news-flow/v2/news?filter=lang:zh-Hans&filter=provider:panews,reuters&client=screener&streaming=false"
news := &[]models.Telegraph{}
// url := "https://news-mediator.tradingview.com/news-flow/v2/news?filter=lang:zh-Hans&filter=area:WLD&client=screener&streaming=false"
url := "https://news-mediator.tradingview.com/news-flow/v2/news?filter=area%3AWLD&filter=lang%3Azh-Hans&client=screener&streaming=false"
resp, err := client.SetTimeout(time.Duration(5)*time.Second).R().
SetHeader("Host", "news-mediator.tradingview.com").
SetHeader("Origin", "https://cn.tradingview.com").
@@ -656,19 +660,81 @@ func (m MarketNewsApi) TradingViewNews() *[]models.TVNews {
Get(url)
if err != nil {
logger.SugaredLogger.Errorf("TradingViewNews err:%s", err.Error())
return TVNews
return news
}
respMap := map[string]any{}
err = json.Unmarshal(resp.Body(), &respMap)
if err != nil {
return TVNews
return news
}
items, err := json.Marshal(respMap["items"])
if err != nil {
return TVNews
return news
}
json.Unmarshal(items, TVNews)
return TVNews
for i, a := range *TVNews {
if i > 10 {
break
}
detail := NewMarketNewsApi().TradingViewNewsDetail(a.Id)
dataTime := time.Unix(int64(a.Published), 0).Local()
description := ""
sentimentResult := ""
if detail != nil {
description = detail.ShortDescription
sentimentResult = AnalyzeSentiment(description).Description
}
if a.Title == "" {
continue
}
telegraph := &models.Telegraph{
Title: a.Title,
Content: description,
DataTime: &dataTime,
IsRed: false,
Time: dataTime.Format("15:04:05"),
Source: "外媒",
Url: fmt.Sprintf("https://cn.tradingview.com/news/%s", a.Id),
SentimentResult: sentimentResult,
}
cnt := int64(0)
db.Dao.Model(telegraph).Where("time=? and title=? and source=?", telegraph.Time, telegraph.Title, "外媒").Count(&cnt)
if cnt > 0 {
continue
}
db.Dao.Model(&models.Telegraph{}).Where("time=? and title=? and source=?", telegraph.Time, telegraph.Title, "外媒").FirstOrCreate(&telegraph)
*news = append(*news, *telegraph)
}
return news
}
func (m MarketNewsApi) TradingViewNewsDetail(id string) *models.TVNewsDetail {
//https://news-headlines.tradingview.com/v3/story?id=panews%3A9be7cf057e3f9%3A0&lang=zh-Hans
newsDetail := &models.TVNewsDetail{}
newsUrl := fmt.Sprintf("https://news-headlines.tradingview.com/v3/story?id=%s&lang=zh-Hans", url.QueryEscape(id))
client := resty.New()
config := GetSettingConfig()
if config.HttpProxyEnabled && config.HttpProxy != "" {
client.SetProxy(config.HttpProxy)
}
request := client.SetTimeout(time.Duration(3) * time.Second).R()
_, err := request.
SetHeader("Host", "news-headlines.tradingview.com").
SetHeader("Origin", "https://cn.tradingview.com").
SetHeader("Referer", "https://cn.tradingview.com/").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0").
//SetHeader("TE", "trailers").
//SetHeader("Priority", "u=4").
//SetHeader("Connection", "keep-alive").
SetResult(newsDetail).
Get(newsUrl)
if err != nil {
logger.SugaredLogger.Errorf("TradingViewNewsDetail err:%s", err.Error())
return newsDetail
}
logger.SugaredLogger.Infof("resp:%+v", newsDetail)
return newsDetail
}
func (m MarketNewsApi) XUEQIUHotStock(size int, marketType string) *[]models.HotItem {
@@ -1031,9 +1097,9 @@ func (m MarketNewsApi) CailianpressWeb(searchWords string) *models.CailianpressW
func (m MarketNewsApi) GetNews24HoursList(source string, limit int) *[]*models.Telegraph {
news := &[]*models.Telegraph{}
if source != "" {
db.Dao.Model(news).Preload("TelegraphTags").Where("source=? and created_at>?", source, time.Now().Add(-24*time.Hour)).Order("created_at desc,is_red desc").Limit(limit).Find(news)
db.Dao.Model(news).Preload("TelegraphTags").Where("source=? and created_at>?", source, time.Now().Add(-24*time.Hour)).Order("data_time desc,is_red desc").Limit(limit).Find(news)
} else {
db.Dao.Model(news).Preload("TelegraphTags").Where("created_at>?", time.Now().Add(-24*time.Hour)).Order("created_at desc,is_red desc").Limit(limit).Find(news)
db.Dao.Model(news).Preload("TelegraphTags").Where("created_at>?", time.Now().Add(-24*time.Hour)).Order("data_time desc,is_red desc").Limit(limit).Find(news)
}
// 内容去重
uniqueNews := make([]*models.Telegraph, 0)

View File

@@ -21,7 +21,12 @@ import (
func TestGetSinaNews(t *testing.T) {
db.Init("../../data/stock.db")
NewMarketNewsApi().GetSinaNews(30)
InitAnalyzeSentiment()
news := NewMarketNewsApi().GetSinaNews(30)
for i, telegraph := range *news {
logger.SugaredLogger.Debugf("key: %+v, value: %+v", i, telegraph)
}
//NewMarketNewsApi().GetNewTelegraph(30)
}
@@ -122,10 +127,8 @@ func TestEMDictCode(t *testing.T) {
func TestTradingViewNews(t *testing.T) {
db.Init("../../data/stock.db")
resp := NewMarketNewsApi().TradingViewNews()
for _, a := range *resp {
logger.SugaredLogger.Debugf("value: %+v", a)
}
InitAnalyzeSentiment()
NewMarketNewsApi().TradingViewNews()
}
func TestXUEQIUHotStock(t *testing.T) {
@@ -247,5 +250,6 @@ func TestGetNewsList2(t *testing.T) {
func TestTelegraphList(t *testing.T) {
db.Init("../../data/stock.db")
InitAnalyzeSentiment()
NewMarketNewsApi().TelegraphList(30)
}

View File

@@ -21,6 +21,13 @@ func NewSearchStockApi(words string) *SearchStockApi {
return &SearchStockApi{words: words}
}
func (s SearchStockApi) SearchStock(pageSize int) map[string]any {
qgqpBId := NewSettingsApi().Config.QgqpBId
if qgqpBId == "" {
return map[string]any{
"code": -1,
"message": "请先获取东财用户标识qgqp_b_id打开浏览器,访问东财网站按F12打开开发人员工具-》网络面板随便点开一个请求复制请求cookie中qgqp_b_id对应的值。保存到设置中的东财唯一标识输入框",
}
}
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").
@@ -32,22 +39,25 @@ func (s SearchStockApi) SearchStock(pageSize int) map[string]any {
"keyWord": "%s",
"pageSize": %d,
"pageNo": 1,
"fingerprint": "02efa8944b1f90fbfe050e1e695a480d",
"fingerprint": "%s",
"gids": [],
"matchWord": "",
"timestamp": "%d",
"shareToGuba": false,
"requestId": "RMd3Y76AJI98axPvdhdbKvbBDVwLlUK61761559950168",
"requestId": "",
"needCorrect": true,
"removedConditionIdList": [],
"xcId": "xc0d61279aad33008260",
"xcId": "",
"ownSelectAll": false,
"dxInfo": [],
"extraCondition": ""
}`, s.words, pageSize, time.Now().Unix())).Post(url)
}`, s.words, pageSize, qgqpBId, time.Now().Unix())).Post(url)
if err != nil {
logger.SugaredLogger.Errorf("SearchStock-err:%+v", err)
return map[string]any{}
return map[string]any{
"code": -1,
"message": err.Error(),
}
}
respMap := map[string]any{}
json.Unmarshal(resp.Body(), &respMap)

View File

@@ -4,14 +4,22 @@ import (
"encoding/json"
"go-stock/backend/db"
"go-stock/backend/logger"
"math"
"testing"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/random"
)
func TestSearchStock(t *testing.T) {
db.Init("../../data/stock.db")
e := convertor.ToString(math.Floor(float64(9*random.RandFloat(0, 1, 12) + 1)))
for i := 0; i < 19; i++ {
e += convertor.ToString(math.Floor(float64(9 * random.RandFloat(0, 1, 12))))
}
logger.SugaredLogger.Infof("e:%s", e)
res := NewSearchStockApi("量比大于2基本面优秀2025年三季报已披露主力连续3日净流入非创业板非科创板非ST").SearchStock(20)
logger.SugaredLogger.Infof("res:%+v", res)
data := res["data"].(map[string]any)

View File

@@ -37,6 +37,7 @@ type Settings struct {
HttpProxy string `json:"httpProxy"`
HttpProxyEnabled bool `json:"httpProxyEnabled"`
EnableAgent bool `json:"enableAgent"`
QgqpBId string `json:"qgqpBId" gorm:"column:qgqp_b_id"`
}
func (receiver Settings) TableName() string {
@@ -108,6 +109,7 @@ func UpdateConfig(s *SettingConfig) string {
"http_proxy": s.HttpProxy,
"http_proxy_enabled": s.HttpProxyEnabled,
"enable_agent": s.EnableAgent,
"qgqp_b_id": s.QgqpBId,
})
//更新AiConfig

View File

@@ -38,14 +38,14 @@ var (
// 负面金融词汇及其权重
negativeFinanceWords = map[string]float64{
"跌": 1.0, "下跌": 2.0, "跌停": 3.0, "熊市": 3.0, "回调": 1.5, "新低": 2.5,
"跌": 2.0, "下跌": 2.0, "跌停": 3.0, "熊市": 3.0, "回调": 2.5, "新低": 2.5,
"利空": 2.5, "减持": 2.0, "卖出": 2.0, "看空": 2.0, "亏损": 2.5,
"下滑": 2.0, "萎缩": 2.0, "不及预期": 2.5, "疲软": 1.5, "恶化": 2.0,
"衰退": 2.0, "跌破": 2.0, "创新低": 3.0, "走弱": 1.5, "下挫": 1.5,
"衰退": 2.0, "跌破": 2.0, "创新低": 3.0, "走弱": 2.5, "下挫": 2.5,
"利空消息": 3.0, "收益下降": 2.5, "利润下滑": 2.5, "业绩不佳": 2.5,
"垃圾股": 2.0, "风险股": 2.0, "弱势": 1.5, "走低": 1.5, "缩量": 2.5,
"大跌": 2.5, "暴跌": 3.0, "崩盘": 3.0, "跳水": 3.0, "重挫": 3.0, "跌超": 2.5, "跌逾": 2.5,
"被抓": 3.0, "被抓捕": 3.0,
"垃圾股": 2.0, "风险股": 2.0, "弱势": 2.5, "走低": 2.5, "缩量": 2.5,
"大跌": 2.5, "暴跌": 3.0, "崩盘": 3.0, "跳水": 3.0, "重挫": 3.0, "跌超": 2.5, "跌逾": 2.5, "跌近": 3.0,
"被抓": 3.0, "被抓捕": 3.0, "回吐": 3.0, "转跌": 3.0,
}
// 否定词,用于反转情感极性
@@ -119,9 +119,9 @@ func InitAnalyzeSentiment() {
// }
//}
tags := &[]models.Tags{}
db.Dao.Model(&models.Tags{}).Find(tags)
db.Dao.Model(&models.Tags{}).Where("type = ?", "subject").Find(tags)
for _, tag := range *tags {
err := seg.AddToken(tag.Name, basefreq+100, "n")
err := seg.ReAddToken(tag.Name, basefreq+100, "n")
if err != nil {
logger.SugaredLogger.Errorf("添加%s失败:%s", tag.Name, err.Error())
}
@@ -141,7 +141,7 @@ func InitAnalyzeSentiment() {
k := strutil.SplitAndTrim(line, " ")
switch len(k) {
case 1:
err = seg.ReAddToken(k[0], 100)
err = seg.ReAddToken(k[0], basefreq)
case 2:
freq, _ := convertor.ToFloat(k[1])
err = seg.ReAddToken(k[0], freq)

View File

@@ -232,15 +232,16 @@ type Prompt struct {
type Telegraph struct {
gorm.Model
Time string `json:"time"`
DataTime *time.Time `json:"dataTime"`
Content string `json:"content"`
DataTime *time.Time `json:"dataTime" gorm:"index"`
Title string `json:"title" gorm:"index"`
Content string `json:"content" gorm:"index"`
SubjectTags []string `json:"subjects" gorm:"-:all"`
StocksTags []string `json:"stocks" gorm:"-:all"`
IsRed bool `json:"isRed"`
IsRed bool `json:"isRed" gorm:"index"`
Url string `json:"url"`
Source string `json:"source"`
Source string `json:"source" gorm:"index"`
TelegraphTags []TelegraphTags `json:"tags" gorm:"-:migration;foreignKey:TelegraphId"`
SentimentResult string `json:"sentimentResult"`
SentimentResult string `json:"sentimentResult" gorm:"index"`
}
type TelegraphTags struct {
gorm.Model
@@ -331,6 +332,22 @@ type TVNews struct {
LogoId string `json:"logo_id"`
} `json:"provider"`
}
type TVNewsDetail struct {
ShortDescription string `json:"shortDescription"`
Tags []struct {
Title string `json:"title"`
Args []struct {
Id string `json:"id"`
Value string `json:"value"`
} `json:"args"`
} `json:"tags"`
Copyright string `json:"copyright"`
Id string `json:"id"`
Title string `json:"title"`
Published int `json:"published"`
Urgency int `json:"urgency"`
StoryPath string `json:"storyPath"`
}
type XUEQIUHot struct {
Data struct {

View File

@@ -1,6 +1,6 @@
<script setup>
import {AnalyzeSentimentWithFreqWeight} from "../../wailsjs/go/main/App";
import {AnalyzeSentimentWithFreqWeight,GlobalStockIndexes} from "../../wailsjs/go/main/App";
import * as echarts from "echarts";
import {onMounted,onUnmounted, ref} from "vue";
import _ from "lodash";
@@ -22,24 +22,60 @@ const { name,darkTheme,kDays ,chartHeight} = defineProps({
default: false
}
})
const common = ref([])
const america = ref([])
const europe = ref([])
const asia = ref([])
const mainIndex = ref([])
const chinaIndex = ref([])
const other = ref([])
const globalStockIndexes = ref(null)
const chartRef = ref(null);
const gaugeChartRef = ref(null);
const triggerAreas=ref(["main","extra","arrow"])
let handleChartInterval=null
let handleIndexInterval=null
onMounted(() => {
handleChart()
getIndex()
handleChartInterval=setInterval(function () {
handleChart()
}, 1000 * 60)
handleIndexInterval=setInterval(function () {
getIndex()
}, 1000 * 2)
})
onUnmounted(()=>{
clearInterval(handleChartInterval)
clearInterval(handleIndexInterval)
})
function getIndex() {
GlobalStockIndexes().then((res) => {
globalStockIndexes.value = res
common.value = res["common"]
america.value = res["america"]
europe.value = res["europe"]
asia.value = res["asia"]
other.value = res["other"]
mainIndex.value=asia.value.filter(function (item) {
return ['上海',"深圳","香港","台湾","北京","东京","首尔","纽约","纳斯达克"].includes(item.location)
}).concat(america.value.filter(function (item) {
return ['上海',"深圳","香港","台湾","北京","东京","首尔","纽约","纳斯达克"].includes(item.location)
}))
chinaIndex.value=asia.value.filter(function (item) {
return ['上海',"深圳","香港","台湾","北京"].includes(item.location)
})
})
}
function handleChart(){
const formatUtil = echarts.format;
AnalyzeSentimentWithFreqWeight("").then((res) => {
console.log(res)
//console.log(res)
const treemapchart = echarts.init(chartRef.value);
const gaugeChart=echarts.init(gaugeChartRef.value);
let data = res['frequencies'].map(item => ({
@@ -117,7 +153,7 @@ function handleChart(){
},
tooltip: {
formatter: function (info) {
var value = info.value;
var value = info.value.toFixed(2);
var frequency = info.data.frequency;
var weight = info.data.weight;
return [
@@ -235,7 +271,7 @@ function handleChart(){
data: [
{
value: res.result.Score*0.2,
name: '市场情绪'
name: '市场情绪强弱'
}
]
}
@@ -247,14 +283,26 @@ function handleChart(){
</script>
<template>
<n-grid :cols="24" :y-gap="0">
<n-gi span="6">
<div ref="gaugeChartRef" style="width: 100%;height: auto;--wails-draggable:no-drag" :style="{height:chartHeight+'px'}" ></div>
</n-gi>
<n-gi span="18">
<div ref="chartRef" style="width: 100%;height: auto;--wails-draggable:no-drag" :style="{height:chartHeight+'px'}" ></div>
</n-gi>
</n-grid>
<n-collapse :trigger-areas="triggerAreas" :default-expanded-names="['1']" display-directive="show">
<n-collapse-item name="1" >
<template #header>
<n-flex>
<n-tag size="small" :bordered="false" v-for="(item, index) in mainIndex" :type="item.zdf>0?'error':'success'"> {{item.name}} {{item.zxj}} <n-number-animation :precision="2" :from="0" :to="item.zdf"/>%</n-tag>
</n-flex>
</template>
<template #header-extra>
主要股指
</template>
<n-grid :cols="24" :y-gap="0">
<n-gi span="6">
<div ref="gaugeChartRef" style="width: 100%;height: auto;--wails-draggable:no-drag" :style="{height:chartHeight+'px'}" ></div>
</n-gi>
<n-gi span="18">
<div ref="chartRef" style="width: 100%;height: auto;--wails-draggable:no-drag" :style="{height:chartHeight+'px'}" ></div>
</n-gi>
</n-grid>
</n-collapse-item>
</n-collapse>
</template>
<style scoped>

View File

@@ -125,7 +125,12 @@ function Search() {
// 计算并设置表格宽度
tableScrollX.value = calculateTableWidth(columns.value);
} else {
message.error(res.msg)
if(res.msg){
message.error(res.msg)
}
if(res.message){
message.error(res.message)
}
}
}).catch(err => {
message.error(err)

View File

@@ -45,6 +45,7 @@ const panelHeight = ref(window.innerHeight - 240)
const telegraphList = ref([])
const sinaNewsList = ref([])
const foreignNewsList = ref([])
const common = ref([])
const america = ref([])
const europe = ref([])
@@ -54,6 +55,7 @@ const globalStockIndexes = ref(null)
const summaryModal = ref(false)
const summaryBTN = ref(true)
const darkTheme = ref(false)
const httpProxyEnabled = ref(false)
const theme = computed(() => {
return darkTheme ? 'dark' : 'light'
})
@@ -96,6 +98,7 @@ onBeforeMount(() => {
GetConfig().then(result => {
summaryBTN.value = result.openAiEnable
darkTheme.value = result.darkTheme
httpProxyEnabled.value = result.httpProxyEnabled
})
GetPromptTemplates("", "").then(res => {
promptTemplates.value = res
@@ -107,13 +110,15 @@ onBeforeMount(() => {
aiConfigs.value = res
aiConfigId.value = res[0].ID
})
GetTelegraphList("财联社电报").then((res) => {
telegraphList.value = res
})
GetTelegraphList("新浪财经").then((res) => {
sinaNewsList.value = res
})
GetTelegraphList("外媒").then((res) => {
foreignNewsList.value = res
})
getIndex();
industryRank();
indexInterval.value = setInterval(() => {
@@ -164,6 +169,14 @@ EventsOn("newSinaNews", (data) => {
sinaNewsList.value.unshift(...data)
}
})
EventsOn("tradingViewNews", (data) => {
if (data!=null) {
for (let i = 0; i < data.length; i++) {
foreignNewsList.value.pop()
}
foreignNewsList.value.unshift(...data)
}
})
//获取页面高度
window.onresize = () => {
@@ -324,6 +337,9 @@ function ReFlesh(source) {
if (source === "新浪财经") {
sinaNewsList.value = res
}
if (source === "外媒") {
foreignNewsList.value = res
}
})
}
</script>
@@ -337,13 +353,17 @@ function ReFlesh(source) {
<AnalyzeMartket :dark-theme="darkTheme" :chart-height="300" :kDays="1" :name="'最近24小时热词'" />
</n-gi>
<n-gi>
<n-grid :cols="2" :y-gap="0">
<n-grid :cols="httpProxyEnabled?3:2" :y-gap="0">
<n-gi>
<news-list :newsList="telegraphList" :header-title="'财联社电报'" @update:message="ReFlesh"></news-list>
</n-gi>
<n-gi>
<news-list :newsList="sinaNewsList" :header-title="'新浪财经'" @update:message="ReFlesh"></news-list>
</n-gi>
<n-gi v-if="httpProxyEnabled">
<news-list :newsList="foreignNewsList" :header-title="'外媒'" @update:message="ReFlesh"></news-list>
</n-gi>
</n-grid>
</n-gi>
</n-grid>

View File

@@ -29,11 +29,19 @@ const updateMessage = () => {
</n-flex>
</template>
<n-list-item v-for="item in newsList">
<n-space justify="start">
<n-space justify="start" >
<n-text justify="start" :bordered="false" :type="item.isRed?'error':'info'">
<n-tag size="small" :type="item.isRed?'error':'warning'" :bordered="false"> {{ item.time }}</n-tag>
{{ item.content }}
<n-gradient-text :size="14" :type="'warning'" :bordered="false">{{ item.title }}</n-gradient-text> <n-text :type="item.isRed?'error':'info'">{{ item.content }}</n-text>
</n-text>
<!-- <n-collapse v-if="item.title">-->
<!-- <n-collapse-item :title="item.title" :name="item.title">-->
<!-- <n-text justify="start" :bordered="false" :type="item.isRed?'error':'info'">-->
<!-- <n-tag size="small" :type="item.isRed?'error':'warning'" :bordered="false"> {{ item.time }}</n-tag>-->
<!-- {{ item.content }}-->
<!-- </n-text>-->
<!-- </n-collapse-item>-->
<!-- </n-collapse>-->
</n-space>
<n-space v-if="item.subjects" style="margin-top: 2px">
<n-tag :bordered="false" type="success" size="small" v-for="sub in item.subjects">

View File

@@ -46,6 +46,7 @@ const formValue = ref({
httpProxy:"",
httpProxyEnabled:false,
enableAgent: false,
qgqpBId: '',
})
// 添加一个新的AI配置到列表
@@ -105,6 +106,7 @@ onMounted(() => {
formValue.value.httpProxy=res.httpProxy;
formValue.value.httpProxyEnabled=res.httpProxyEnabled;
formValue.value.enableAgent = res.enableAgent;
formValue.value.qgqpBId = res.qgqpBId;
})
@@ -145,6 +147,7 @@ function saveConfig() {
httpProxy:formValue.value.httpProxy,
httpProxyEnabled:formValue.value.httpProxyEnabled,
enableAgent: formValue.value.enableAgent,
qgqpBId: formValue.value.qgqpBId
})
if (config.sponsorCode) {
@@ -235,6 +238,7 @@ function importConfig() {
formValue.value.httpProxy=config.httpProxy
formValue.value.httpProxyEnabled=config.httpProxyEnabled
formValue.value.enableAgent = config.enableAgent
formValue.value.qgqpBId = config.qgqpBId
};
reader.readAsText(file);
};
@@ -328,6 +332,9 @@ function deletePrompt(ID) {
<n-form-item-gi :span="3" label="AI智能体" path="enableAgent">
<n-switch v-model:value="formValue.enableAgent"/>
</n-form-item-gi>
<n-form-item-gi :span="11" label="东财唯一标识:" path="qgqpBId">
<n-input type="text" placeholder="东财唯一标识" v-model:value="formValue.qgqpBId" clearable/>
</n-form-item-gi>
<n-form-item-gi :span="11" label="赞助码:" path="sponsorCode">
<n-input-group>

View File

@@ -394,6 +394,7 @@ export namespace data {
httpProxy: string;
httpProxyEnabled: boolean;
enableAgent: boolean;
qgqpBId: string;
aiConfigs: AIConfig[];
static createFrom(source: any = {}) {
@@ -430,6 +431,7 @@ export namespace data {
this.httpProxy = source["httpProxy"];
this.httpProxyEnabled = source["httpProxyEnabled"];
this.enableAgent = source["enableAgent"];
this.qgqpBId = source["qgqpBId"];
this.aiConfigs = this.convertValues(source["aiConfigs"], AIConfig);
}