From 9a6e210bae61509816785c4119047d819d37fe4b Mon Sep 17 00:00:00 2001 From: ArvinLovegood Date: Sun, 9 Mar 2025 16:35:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(fund):=E6=B7=BB=E5=8A=A0=E5=9F=BA=E9=87=91?= =?UTF-8?q?=E7=9B=91=E6=8E=A7=E5=92=8C=E6=9F=A5=E8=AF=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增基金数据 API,实现基金信息爬取和数据库操作 - 添加基金监控逻辑,定期更新基金净值信息 - 实现基金列表查询、关注和取消关注功能 - 新增基金相关前端组件,展示基金信息和操作 --- app.go | 37 ++- backend/data/fund_data_api.go | 379 +++++++++++++++++++++++++++++ backend/data/fund_data_api_test.go | 23 ++ backend/data/utils_test.go | 4 + frontend/src/App.vue | 14 +- frontend/src/components/fund.vue | 207 ++++++++++++++++ frontend/src/components/stock.vue | 6 +- frontend/src/router/router.js | 2 + frontend/wailsjs/go/main/App.d.ts | 8 + frontend/wailsjs/go/main/App.js | 16 ++ frontend/wailsjs/go/models.ts | 141 +++++++++++ main.go | 2 + 12 files changed, 829 insertions(+), 10 deletions(-) create mode 100644 backend/data/fund_data_api.go create mode 100644 backend/data/fund_data_api_test.go create mode 100644 frontend/src/components/fund.vue diff --git a/app.go b/app.go index 917fc79..7cd70de 100644 --- a/app.go +++ b/app.go @@ -106,7 +106,15 @@ func (a *App) domReady(ctx context.Context) { defer ticker.Stop() for range ticker.C { MonitorStockPrices(a) + } + }() + //刷新基金净值信息 + go func() { + ticker := time.NewTicker(time.Second * time.Duration(60)) + defer ticker.Stop() + for range ticker.C { + MonitorFundPrices(a) } }() @@ -123,7 +131,8 @@ func (a *App) domReady(ctx context.Context) { }() go runtime.EventsEmit(a.ctx, "telegraph", refreshTelegraphList()) go MonitorStockPrices(a) - + go MonitorFundPrices(a) + go data.NewFundApi().AllFund() //检查新版本 go func() { a.CheckUpdate() @@ -267,6 +276,19 @@ func IsUSTradingTime(date time.Time) bool { return false } +func MonitorFundPrices(a *App) { + dest := &[]data.FollowedFund{} + db.Dao.Model(&data.FollowedFund{}).Find(dest) + for _, follow := range *dest { + _, err := data.NewFundApi().CrawlFundBasic(follow.Code) + if err != nil { + logger.SugaredLogger.Errorf("获取基金基本信息失败,基金代码:%s,错误信息:%s", follow.Code, err.Error()) + continue + } + data.NewFundApi().CrawlFundNetEstimatedUnit(follow.Code) + data.NewFundApi().CrawlFundNetUnitValue(follow.Code) + } +} func MonitorStockPrices(a *App) { dest := &[]data.FollowedStock{} @@ -731,3 +753,16 @@ func (a *App) ShareAnalysis(stockCode, stockName string) string { } return "获取分析结果失败" } + +func (a *App) GetfundList(key string) []data.FundBasic { + return data.NewFundApi().GetFundList(key) +} +func (a *App) GetFollowedFund() []data.FollowedFund { + return data.NewFundApi().GetFollowedFund() +} +func (a *App) FollowFund(fundCode string) string { + return data.NewFundApi().FollowFund(fundCode) +} +func (a *App) UnFollowFund(fundCode string) string { + return data.NewFundApi().UnFollowFund(fundCode) +} diff --git a/backend/data/fund_data_api.go b/backend/data/fund_data_api.go new file mode 100644 index 0000000..d11f817 --- /dev/null +++ b/backend/data/fund_data_api.go @@ -0,0 +1,379 @@ +package data + +import ( + "context" + "encoding/json" + "fmt" + "github.com/duke-git/lancet/v2/convertor" + "github.com/duke-git/lancet/v2/strutil" + "github.com/go-resty/resty/v2" + "go-stock/backend/db" + "go-stock/backend/logger" + "strconv" + "strings" + "time" + + "github.com/PuerkitoBio/goquery" + "gorm.io/gorm" +) + +type FundApi struct { + client *resty.Client + config *Settings +} + +func NewFundApi() *FundApi { + return &FundApi{ + client: resty.New(), + config: getConfig(), + } +} + +type FollowedFund struct { + gorm.Model + Code string `json:"code" gorm:"index"` // 基金代码 + Name string `json:"name"` // 基金简称 + + NetUnitValue *float64 `json:"netUnitValue"` // 单位净值 + NetUnitValueDate string `json:"netUnitValueDate"` // 单位净值日期 + NetEstimatedUnit *float64 `json:"netEstimatedUnit"` // 估算单位净值 + NetEstimatedTime string `json:"netEstimatedUnitTime"` // 估算单位净值日期 + NetAccumulated *float64 `json:"netAccumulated"` // 累计净值 + FundBasic FundBasic `json:"fundBasic" gorm:"foreignKey:Code;references:Code"` +} + +func (FollowedFund) TableName() string { + return "followed_fund" +} + +// FundBasic 基金基本信息结构体 +type FundBasic struct { + gorm.Model + Code string `json:"code" gorm:"index"` // 基金代码 + Name string `json:"name"` // 基金简称 + FullName string `json:"fullName"` // 基金全称 + Type string `json:"type"` // 基金类型 + Establishment string `json:"establishment"` // 成立日期 + Scale string `json:"scale"` // 最新规模(亿元) + Company string `json:"company"` // 基金管理人 + Manager string `json:"manager"` // 基金经理 + Rating string `json:"rating"` //基金评级 + TrackingTarget string `json:"trackingTarget"` //跟踪标的 + + NetUnitValue *float64 `json:"netUnitValue"` // 单位净值 + NetUnitValueDate string `json:"netUnitValueDate"` // 单位净值日期 + NetEstimatedUnit *float64 `json:"netEstimatedUnit"` // 估算单位净值 + NetEstimatedTime string `json:"netEstimatedUnitTime"` // 估算单位净值日期 + NetAccumulated *float64 `json:"netAccumulated"` // 累计净值 + + //净值涨跌幅: 近1月,近3月,近6月,近1年,近3年,近5年,今年来,成立来 + NetGrowth1 *float64 `json:"netGrowth1"` //近1月 + NetGrowth3 *float64 `json:"netGrowth3"` //近3月 + NetGrowth6 *float64 `json:"netGrowth6"` //近6月 + NetGrowth12 *float64 `json:"netGrowth12"` //近1年 + NetGrowth36 *float64 `json:"netGrowth36"` //近3年 + NetGrowth60 *float64 `json:"netGrowth60"` //近5年 + NetGrowthYTD *float64 `json:"netGrowthYTD"` //今年来 + NetGrowthAll *float64 `json:"netGrowthAll"` //成立来 +} + +func (FundBasic) TableName() string { + return "fund_basic" +} + +// CrawlFundBasic 爬取基金基本信息 +func (f *FundApi) CrawlFundBasic(fundCode string) (*FundBasic, error) { + crawler := CrawlerApi{ + crawlerBaseInfo: CrawlerBaseInfo{ + Name: "天天基金", + BaseUrl: "http://fund.eastmoney.com", + Headers: map[string]string{"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"}, + }, + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(f.config.CrawlTimeOut)*time.Second) + defer cancel() + + crawler = crawler.NewCrawler(ctx, crawler.crawlerBaseInfo) + url := fmt.Sprintf("%s/%s.html", crawler.crawlerBaseInfo.BaseUrl, fundCode) + logger.SugaredLogger.Infof("CrawlFundBasic url:%s", url) + + // 使用现有爬虫框架解析页面 + htmlContent, ok := crawler.GetHtml(url, ".merchandiseDetail", true) + if !ok { + return nil, fmt.Errorf("页面解析失败") + } + + fund := &FundBasic{Code: fundCode} + doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent)) + if err != nil { + return nil, err + } + + // 解析基础信息 + name := doc.Find(".merchandiseDetail .fundDetail-tit").First().Text() + fund.Name = strings.TrimSpace(strutil.ReplaceWithMap(name, map[string]string{"查看相关ETF>": ""})) + logger.SugaredLogger.Infof("基金名称:%s", fund.Name) + + doc.Find(".infoOfFund table td ").Each(func(i int, s *goquery.Selection) { + text := strutil.RemoveWhiteSpace(s.Text(), true) + logger.SugaredLogger.Infof("基金信息:%+v", text) + defer func() { + if r := recover(); r != nil { + logger.SugaredLogger.Errorf("panic: %v", r) + } + }() + splitEx := strutil.SplitEx(text, ":", true) + if strutil.ContainsAny(text, []string{"基金类型", "类型"}) { + fund.Type = splitEx[1] + } + if strutil.ContainsAny(text, []string{"成立日期", "成立日"}) { + fund.Establishment = splitEx[1] + } + if strutil.ContainsAny(text, []string{"基金规模", "规模"}) { + fund.Scale = splitEx[1] + } + if strutil.ContainsAny(text, []string{"管理人", "基金公司"}) { + fund.Company = splitEx[1] + } + if strutil.ContainsAny(text, []string{"基金经理", "经理人"}) { + fund.Manager = splitEx[1] + } + if strutil.ContainsAny(text, []string{"基金评级", "评级"}) { + fund.Rating = splitEx[1] + } + if strutil.ContainsAny(text, []string{"跟踪标的", "标的"}) { + fund.TrackingTarget = splitEx[1] + } + }) + + //获取基金净值涨跌幅信息 + doc.Find(".dataOfFund dl > dd").Each(func(i int, s *goquery.Selection) { + text := strutil.RemoveWhiteSpace(s.Text(), true) + logger.SugaredLogger.Infof("净值涨跌幅信息:%+v", text) + defer func() { + if r := recover(); r != nil { + logger.SugaredLogger.Errorf("panic: %v", r) + } + }() + splitEx := strutil.SplitAndTrim(text, ":", "%") + toFloat, err1 := convertor.ToFloat(splitEx[1]) + if err1 != nil { + logger.SugaredLogger.Errorf("转换失败:%+v", err) + return + } + logger.SugaredLogger.Infof("净值涨跌幅信息:%+v", toFloat) + if strutil.ContainsAny(text, []string{"近1月"}) { + fund.NetGrowth1 = &toFloat + } + if strutil.ContainsAny(text, []string{"近3月"}) { + fund.NetGrowth3 = &toFloat + } + if strutil.ContainsAny(text, []string{"近6月"}) { + fund.NetGrowth6 = &toFloat + } + if strutil.ContainsAny(text, []string{"近1年"}) { + fund.NetGrowth12 = &toFloat + } + if strutil.ContainsAny(text, []string{"近3年"}) { + fund.NetGrowth36 = &toFloat + } + if strutil.ContainsAny(text, []string{"近5年"}) { + fund.NetGrowth60 = &toFloat + } + if strutil.ContainsAny(text, []string{"今年来"}) { + fund.NetGrowthYTD = &toFloat + } + if strutil.ContainsAny(text, []string{"成立来"}) { + fund.NetGrowthAll = &toFloat + } + }) + doc.Find(".dataOfFund dl > dd.dataNums,.dataOfFund dl > dt").Each(func(i int, s *goquery.Selection) { + text := s.Text() + defer func() { + if r := recover(); r != nil { + logger.SugaredLogger.Errorf("panic: %v", r) + } + }() + logger.SugaredLogger.Infof("净值信息:%+v", text) + }) + + logger.SugaredLogger.Infof("基金信息:%+v", fund) + + count := int64(0) + db.Dao.Model(fund).Where("code=?", fund.Code).Count(&count) + if count == 0 { + db.Dao.Create(fund) + } else { + db.Dao.Model(fund).Where("code=?", fund.Code).Updates(fund) + } + + return fund, nil +} + +func (f *FundApi) GetFundList(key string) []FundBasic { + var funds []FundBasic + db.Dao.Where("code like ? or name like ?", "%"+key+"%", "%"+key+"%").Limit(10).Find(&funds) + return funds +} + +func (f *FundApi) GetFollowedFund() []FollowedFund { + var funds []FollowedFund + db.Dao.Preload("FundBasic").Find(&funds) + return funds +} +func (f *FundApi) FollowFund(fundCode string) string { + var fund FundBasic + db.Dao.Where("code=?", fundCode).First(&fund) + if fund.Code != "" { + follow := &FollowedFund{ + Code: fundCode, + Name: fund.Name, + } + err := db.Dao.Model(follow).Where("code = ?", fundCode).FirstOrCreate(follow, "code", fund.Code).Error + if err != nil { + return "关注失败" + } + return "关注成功" + } else { + return "基金信息不存在" + } +} +func (f *FundApi) UnFollowFund(fundCode string) string { + var fund FollowedFund + db.Dao.Where("code=?", fundCode).First(&fund) + if fund.Code != "" { + err := db.Dao.Model(&fund).Delete(&fund).Error + if err != nil { + return "取消关注失败" + } + return "取消关注成功" + } else { + return "基金信息不存在" + } +} + +func (f *FundApi) AllFund() { + response, err := f.client.SetTimeout(time.Duration(f.config.CrawlTimeOut)*time.Second).R(). + SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"). + Get("https://fund.eastmoney.com/allfund.html") + if err != nil { + return + } + //中文编码 + htmlContent := GB18030ToUTF8(response.Body()) + + doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent)) + cnt := 0 + doc.Find("ul.num_right li").Each(func(i int, s *goquery.Selection) { + text := strutil.SplitEx(s.Text(), "|", true) + if len(text) > 0 { + cnt++ + name := text[0] + str := strutil.SplitAndTrim(name, ")", "(", ")") + logger.SugaredLogger.Infof("%d,基金信息 code:%s,name:%s", cnt, str[0], str[1]) + //f.CrawlFundBasic(str[0]) + + fund := &FundBasic{ + Code: str[0], + Name: str[1], + } + count := int64(0) + db.Dao.Model(fund).Where("code=?", fund.Code).Count(&count) + if count == 0 { + db.Dao.Create(fund) + } else { + db.Dao.Model(fund).Where("code=?", fund.Code).Updates(fund) + } + + } + }) + +} + +type FundNetUnitValue struct { + Fundcode string `json:"fundcode"` + Name string `json:"name"` + Jzrq string `json:"jzrq"` + Dwjz string `json:"dwjz"` + Gsz string `json:"gsz"` + Gszzl string `json:"gszzl"` + Gztime string `json:"gztime"` +} + +// CrawlFundNetEstimatedUnit 爬取净值估算值 +func (f *FundApi) CrawlFundNetEstimatedUnit(code string) { + var fundNetUnitValue FundNetUnitValue + response, err := f.client.SetTimeout(time.Duration(f.config.CrawlTimeOut)*time.Second).R(). + SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"). + SetHeader("Referer", "https://fund.eastmoney.com/"). + SetQueryParams(map[string]string{"rt": strconv.FormatInt(time.Now().UnixMilli(), 10)}). + Get(fmt.Sprintf("https://fundgz.1234567.com.cn/js/%s.js", code)) + if err != nil { + logger.SugaredLogger.Errorf("err:%s", err.Error()) + return + } + if response.StatusCode() == 200 { + htmlContent := string(response.Body()) + logger.SugaredLogger.Infof("htmlContent:%s", htmlContent) + if strings.Contains(htmlContent, "jsonpgz") { + htmlContent = strutil.Trim(htmlContent, "jsonpgz(", ");") + htmlContent = strutil.Trim(htmlContent, ");") + logger.SugaredLogger.Infof("基金净值信息:%s", htmlContent) + err := json.Unmarshal([]byte(htmlContent), &fundNetUnitValue) + if err != nil { + logger.SugaredLogger.Errorf("json.Unmarshal error:%s", err.Error()) + return + } + fund := &FollowedFund{ + Code: fundNetUnitValue.Fundcode, + Name: fundNetUnitValue.Name, + NetEstimatedTime: fundNetUnitValue.Gztime, + } + netEstimatedUnit, err := convertor.ToFloat(fundNetUnitValue.Gsz) + if err == nil { + fund.NetEstimatedUnit = &netEstimatedUnit + } + db.Dao.Model(fund).Where("code=?", fund.Code).Updates(fund) + } + } +} + +// CrawlFundNetUnitValue 爬取净值 +func (f *FundApi) CrawlFundNetUnitValue(code string) { + // var fundNetUnitValue FundNetUnitValue + url := fmt.Sprintf("http://hq.sinajs.cn/rn=%d&list=f_%s", time.Now().UnixMilli(), code) + logger.SugaredLogger.Infof("url:%s", url) + response, err := f.client.SetTimeout(time.Duration(f.config.CrawlTimeOut)*time.Second).R(). + SetHeader("Host", "hq.sinajs.cn"). + SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0"). + SetHeader("Referer", "https://finance.sina.com.cn"). + Get(url) + if err != nil { + logger.SugaredLogger.Errorf("err:%s", err.Error()) + return + } + if response.StatusCode() == 200 { + data := string(GB18030ToUTF8(response.Body())) + logger.SugaredLogger.Infof("data:%s", data) + datas := strutil.SplitAndTrim(data, "=", "\"") + if len(datas) >= 2 { + //codex := strings.Split(datas[0], "hq_str_f_")[1] + parts := strutil.SplitAndTrim(datas[1], ",", "\"") + logger.SugaredLogger.Infof("parts:%s", parts) + val, err := convertor.ToFloat(parts[1]) + if err != nil { + logger.SugaredLogger.Errorf("err:%s", err.Error()) + return + } + fund := &FollowedFund{ + Name: parts[0], + Code: code, + NetUnitValue: &val, + NetUnitValueDate: parts[4], + } + db.Dao.Model(fund).Where("code=?", fund.Code).Updates(fund) + } + + } +} diff --git a/backend/data/fund_data_api_test.go b/backend/data/fund_data_api_test.go new file mode 100644 index 0000000..caf9d66 --- /dev/null +++ b/backend/data/fund_data_api_test.go @@ -0,0 +1,23 @@ +package data + +import ( + "go-stock/backend/db" + "testing" +) + +func TestCrawlFundBasic(t *testing.T) { + db.Init("../../data/stock.db") + db.Dao.AutoMigrate(&FundBasic{}) + api := NewFundApi() + + //api.CrawlFundBasic("510630") + //api.CrawlFundBasic("159688") + // + api.AllFund() +} + +func TestCrawlFundNetUnitValue(t *testing.T) { + db.Init("../../data/stock.db") + api := NewFundApi() + api.CrawlFundNetUnitValue("016533") +} diff --git a/backend/data/utils_test.go b/backend/data/utils_test.go index fb7974b..a7e02cf 100644 --- a/backend/data/utils_test.go +++ b/backend/data/utils_test.go @@ -1,7 +1,9 @@ package data import ( + "github.com/duke-git/lancet/v2/slice" "go-stock/backend/logger" + "os" "testing" ) @@ -40,4 +42,6 @@ func TestReplaceSensitiveWords(t *testing.T) { txt := "新 希 望习近平" txt2 := ReplaceSensitiveWords(txt) logger.SugaredLogger.Infof("ReplaceSensitiveWords(%s)", txt2) + + os.WriteFile("words.txt", []byte(slice.Join(SensitiveWords, "\n")), 0644) } diff --git a/frontend/src/App.vue b/frontend/src/App.vue index b25998b..2147ce6 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -52,20 +52,22 @@ const menuOptions = ref([ { label: () => h( - NGradientText, + RouterLink, { - type: 'warning', - style: { - 'text-decoration': 'line-through', + to: { + name: 'fund', + params: { + id: 'zh-CN' + }, } }, - { default: () => '基金自选' } + { default: () => '基金自选',} ), key: 'fund', icon: renderIcon(SparklesOutline), children:[ { - label: ()=> h(NText, {type:realtimeProfit.value>0?'error':'success'},{ default: () => '敬请期待!'}), + label: ()=> h(NText, {type:realtimeProfit.value>0?'error':'success'},{ default: () => '功能完善中!'}), key: 'realtimeProfit', show: realtimeProfit.value, icon: renderIcon(AlarmOutline), diff --git a/frontend/src/components/fund.vue b/frontend/src/components/fund.vue new file mode 100644 index 0000000..8faffc0 --- /dev/null +++ b/frontend/src/components/fund.vue @@ -0,0 +1,207 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/stock.vue b/frontend/src/components/stock.vue index f9fd8dd..8537b6e 100644 --- a/frontend/src/components/stock.vue +++ b/frontend/src/components/stock.vue @@ -394,7 +394,7 @@ function getStockList(value){ } async function updateData(result) { - console.log("stock_price",result['日期'],result['时间'],result['股票代码'],result['股票名称'],result['当前价格'],result['盘前盘后']) + //console.log("stock_price",result['日期'],result['时间'],result['股票代码'],result['股票名称'],result['当前价格'],result['盘前盘后']) if(result["当前价格"]<=0){ result["当前价格"]=result["卖一报价"] @@ -842,7 +842,7 @@ function share(code,name){ -
+
@@ -855,7 +855,7 @@ function share(code,name){ placeholder="股票指数名称/代码/弹幕" clearable @update-value="getStockList" :on-select="onSelect"/> -  关注该股票 +  关注  发送弹幕 diff --git a/frontend/src/router/router.js b/frontend/src/router/router.js index 7ad0c0f..77320a0 100644 --- a/frontend/src/router/router.js +++ b/frontend/src/router/router.js @@ -3,9 +3,11 @@ import { createMemoryHistory, createRouter } from 'vue-router' import stockView from '../components/stock.vue' import settingsView from '../components/settings.vue' import about from "../components/about.vue"; +import fundView from "../components/fund.vue"; const routes = [ { path: '/', component: stockView,name: 'stock' }, + { path: '/fund', component: fundView,name: 'fund' }, { path: '/settings/:id', component: settingsView,name: 'settings' }, { path: '/about', component: about,name: 'about' }, ] diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 9ae0747..9c795b1 100644 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -9,16 +9,22 @@ export function ExportConfig():Promise; export function Follow(arg1:string):Promise; +export function FollowFund(arg1:string):Promise; + export function GetAIResponseResult(arg1:string):Promise; export function GetConfig():Promise; export function GetFollowList():Promise; +export function GetFollowedFund():Promise>; + export function GetStockList(arg1:string):Promise>; export function GetVersionInfo():Promise; +export function GetfundList(arg1:string):Promise>; + export function Greet(arg1:string):Promise; export function NewChatStream(arg1:string,arg2:string,arg3:string):Promise; @@ -39,4 +45,6 @@ export function ShareAnalysis(arg1:string,arg2:string):Promise; export function UnFollow(arg1:string):Promise; +export function UnFollowFund(arg1:string):Promise; + export function UpdateConfig(arg1:data.Settings):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 724bf3f..e2e7f2d 100644 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -14,6 +14,10 @@ export function Follow(arg1) { return window['go']['main']['App']['Follow'](arg1); } +export function FollowFund(arg1) { + return window['go']['main']['App']['FollowFund'](arg1); +} + export function GetAIResponseResult(arg1) { return window['go']['main']['App']['GetAIResponseResult'](arg1); } @@ -26,6 +30,10 @@ export function GetFollowList() { return window['go']['main']['App']['GetFollowList'](); } +export function GetFollowedFund() { + return window['go']['main']['App']['GetFollowedFund'](); +} + export function GetStockList(arg1) { return window['go']['main']['App']['GetStockList'](arg1); } @@ -34,6 +42,10 @@ export function GetVersionInfo() { return window['go']['main']['App']['GetVersionInfo'](); } +export function GetfundList(arg1) { + return window['go']['main']['App']['GetfundList'](arg1); +} + export function Greet(arg1) { return window['go']['main']['App']['Greet'](arg1); } @@ -74,6 +86,10 @@ export function UnFollow(arg1) { return window['go']['main']['App']['UnFollow'](arg1); } +export function UnFollowFund(arg1) { + return window['go']['main']['App']['UnFollowFund'](arg1); +} + export function UpdateConfig(arg1) { return window['go']['main']['App']['UpdateConfig'](arg1); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index de4025c..0d1d09d 100644 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -1,5 +1,146 @@ export namespace data { + export class FundBasic { + ID: number; + // Go type: time + CreatedAt: any; + // Go type: time + UpdatedAt: any; + // Go type: gorm + DeletedAt: any; + code: string; + name: string; + fullName: string; + type: string; + establishment: string; + scale: string; + company: string; + manager: string; + rating: string; + trackingTarget: string; + netUnitValue?: number; + netUnitValueDate: string; + netEstimatedUnit?: number; + netEstimatedUnitTime: string; + netAccumulated?: number; + netGrowth1?: number; + netGrowth3?: number; + netGrowth6?: number; + netGrowth12?: number; + netGrowth36?: number; + netGrowth60?: number; + netGrowthYTD?: number; + netGrowthAll?: number; + + static createFrom(source: any = {}) { + return new FundBasic(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.ID = source["ID"]; + this.CreatedAt = this.convertValues(source["CreatedAt"], null); + this.UpdatedAt = this.convertValues(source["UpdatedAt"], null); + this.DeletedAt = this.convertValues(source["DeletedAt"], null); + this.code = source["code"]; + this.name = source["name"]; + this.fullName = source["fullName"]; + this.type = source["type"]; + this.establishment = source["establishment"]; + this.scale = source["scale"]; + this.company = source["company"]; + this.manager = source["manager"]; + this.rating = source["rating"]; + this.trackingTarget = source["trackingTarget"]; + this.netUnitValue = source["netUnitValue"]; + this.netUnitValueDate = source["netUnitValueDate"]; + this.netEstimatedUnit = source["netEstimatedUnit"]; + this.netEstimatedUnitTime = source["netEstimatedUnitTime"]; + this.netAccumulated = source["netAccumulated"]; + this.netGrowth1 = source["netGrowth1"]; + this.netGrowth3 = source["netGrowth3"]; + this.netGrowth6 = source["netGrowth6"]; + this.netGrowth12 = source["netGrowth12"]; + this.netGrowth36 = source["netGrowth36"]; + this.netGrowth60 = source["netGrowth60"]; + this.netGrowthYTD = source["netGrowthYTD"]; + this.netGrowthAll = source["netGrowthAll"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice && a.map) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } + } + export class FollowedFund { + ID: number; + // Go type: time + CreatedAt: any; + // Go type: time + UpdatedAt: any; + // Go type: gorm + DeletedAt: any; + code: string; + name: string; + netUnitValue?: number; + netUnitValueDate: string; + netEstimatedUnit?: number; + netEstimatedUnitTime: string; + netAccumulated?: number; + fundBasic: FundBasic; + + static createFrom(source: any = {}) { + return new FollowedFund(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.ID = source["ID"]; + this.CreatedAt = this.convertValues(source["CreatedAt"], null); + this.UpdatedAt = this.convertValues(source["UpdatedAt"], null); + this.DeletedAt = this.convertValues(source["DeletedAt"], null); + this.code = source["code"]; + this.name = source["name"]; + this.netUnitValue = source["netUnitValue"]; + this.netUnitValueDate = source["netUnitValueDate"]; + this.netEstimatedUnit = source["netEstimatedUnit"]; + this.netEstimatedUnitTime = source["netEstimatedUnitTime"]; + this.netAccumulated = source["netAccumulated"]; + this.fundBasic = this.convertValues(source["fundBasic"], FundBasic); + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice && a.map) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } + } + export class Settings { ID: number; // Go type: time diff --git a/main.go b/main.go index 27da95d..962f715 100644 --- a/main.go +++ b/main.go @@ -63,6 +63,8 @@ func main() { db.Dao.AutoMigrate(&models.AIResponseResult{}) db.Dao.AutoMigrate(&models.StockInfoHK{}) db.Dao.AutoMigrate(&models.StockInfoUS{}) + db.Dao.AutoMigrate(&data.FollowedFund{}) + db.Dao.AutoMigrate(&data.FundBasic{}) if stocksBin != nil && len(stocksBin) > 0 { go initStockData()