mirror of
https://github.com/ArvinLovegood/go-stock.git
synced 2025-07-19 00:00:09 +08:00
feat(fund):添加基金监控和查询功能
- 新增基金数据 API,实现基金信息爬取和数据库操作 - 添加基金监控逻辑,定期更新基金净值信息 - 实现基金列表查询、关注和取消关注功能 - 新增基金相关前端组件,展示基金信息和操作
This commit is contained in:
parent
1b31ff04df
commit
9a6e210bae
37
app.go
37
app.go
@ -106,7 +106,15 @@ func (a *App) domReady(ctx context.Context) {
|
|||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
MonitorStockPrices(a)
|
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 runtime.EventsEmit(a.ctx, "telegraph", refreshTelegraphList())
|
||||||
go MonitorStockPrices(a)
|
go MonitorStockPrices(a)
|
||||||
|
go MonitorFundPrices(a)
|
||||||
|
go data.NewFundApi().AllFund()
|
||||||
//检查新版本
|
//检查新版本
|
||||||
go func() {
|
go func() {
|
||||||
a.CheckUpdate()
|
a.CheckUpdate()
|
||||||
@ -267,6 +276,19 @@ func IsUSTradingTime(date time.Time) bool {
|
|||||||
|
|
||||||
return false
|
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) {
|
func MonitorStockPrices(a *App) {
|
||||||
dest := &[]data.FollowedStock{}
|
dest := &[]data.FollowedStock{}
|
||||||
@ -731,3 +753,16 @@ func (a *App) ShareAnalysis(stockCode, stockName string) string {
|
|||||||
}
|
}
|
||||||
return "获取分析结果失败"
|
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)
|
||||||
|
}
|
||||||
|
379
backend/data/fund_data_api.go
Normal file
379
backend/data/fund_data_api.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
23
backend/data/fund_data_api_test.go
Normal file
23
backend/data/fund_data_api_test.go
Normal file
@ -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")
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/duke-git/lancet/v2/slice"
|
||||||
"go-stock/backend/logger"
|
"go-stock/backend/logger"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,4 +42,6 @@ func TestReplaceSensitiveWords(t *testing.T) {
|
|||||||
txt := "新 希 望习近平"
|
txt := "新 希 望习近平"
|
||||||
txt2 := ReplaceSensitiveWords(txt)
|
txt2 := ReplaceSensitiveWords(txt)
|
||||||
logger.SugaredLogger.Infof("ReplaceSensitiveWords(%s)", txt2)
|
logger.SugaredLogger.Infof("ReplaceSensitiveWords(%s)", txt2)
|
||||||
|
|
||||||
|
os.WriteFile("words.txt", []byte(slice.Join(SensitiveWords, "\n")), 0644)
|
||||||
}
|
}
|
||||||
|
@ -52,20 +52,22 @@ const menuOptions = ref([
|
|||||||
{
|
{
|
||||||
label: () =>
|
label: () =>
|
||||||
h(
|
h(
|
||||||
NGradientText,
|
RouterLink,
|
||||||
{
|
{
|
||||||
type: 'warning',
|
to: {
|
||||||
style: {
|
name: 'fund',
|
||||||
'text-decoration': 'line-through',
|
params: {
|
||||||
|
id: 'zh-CN'
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ default: () => '基金自选' }
|
{ default: () => '基金自选',}
|
||||||
),
|
),
|
||||||
key: 'fund',
|
key: 'fund',
|
||||||
icon: renderIcon(SparklesOutline),
|
icon: renderIcon(SparklesOutline),
|
||||||
children:[
|
children:[
|
||||||
{
|
{
|
||||||
label: ()=> h(NText, {type:realtimeProfit.value>0?'error':'success'},{ default: () => '敬请期待!'}),
|
label: ()=> h(NText, {type:realtimeProfit.value>0?'error':'success'},{ default: () => '功能完善中!'}),
|
||||||
key: 'realtimeProfit',
|
key: 'realtimeProfit',
|
||||||
show: realtimeProfit.value,
|
show: realtimeProfit.value,
|
||||||
icon: renderIcon(AlarmOutline),
|
icon: renderIcon(AlarmOutline),
|
||||||
|
207
frontend/src/components/fund.vue
Normal file
207
frontend/src/components/fund.vue
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
<script setup>
|
||||||
|
import {h, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from "vue";
|
||||||
|
import {Add, ChatboxOutline} from "@vicons/ionicons5";
|
||||||
|
import {NButton, NEllipsis, NText, useMessage} from "naive-ui";
|
||||||
|
import {
|
||||||
|
FollowFund,
|
||||||
|
GetConfig,
|
||||||
|
GetFollowedFund,
|
||||||
|
GetfundList,
|
||||||
|
GetVersionInfo,
|
||||||
|
UnFollowFund
|
||||||
|
} from "../../wailsjs/go/main/App";
|
||||||
|
import vueDanmaku from 'vue3-danmaku'
|
||||||
|
|
||||||
|
const danmus = ref([])
|
||||||
|
const ws = ref(null)
|
||||||
|
const icon = ref(null)
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const data = reactive({
|
||||||
|
modelName:"",
|
||||||
|
chatId: "",
|
||||||
|
question:"",
|
||||||
|
name: "",
|
||||||
|
code: "",
|
||||||
|
fenshiURL:"",
|
||||||
|
kURL:"",
|
||||||
|
fullscreen: false,
|
||||||
|
airesult: "",
|
||||||
|
openAiEnable: false,
|
||||||
|
loading: true,
|
||||||
|
enableDanmu: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const followList=ref([])
|
||||||
|
const options=ref([])
|
||||||
|
const ticker=ref({})
|
||||||
|
|
||||||
|
onBeforeMount(()=>{
|
||||||
|
GetConfig().then(result => {
|
||||||
|
if (result.openAiEnable) {
|
||||||
|
data.openAiEnable = true
|
||||||
|
}
|
||||||
|
if (result.enableDanmu) {
|
||||||
|
data.enableDanmu = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
GetFollowedFund().then(result => {
|
||||||
|
followList.value = result
|
||||||
|
console.log("followList",followList.value)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
GetVersionInfo().then((res) => {
|
||||||
|
icon.value = res.icon;
|
||||||
|
});
|
||||||
|
// 创建 WebSocket 连接
|
||||||
|
ws.value = new WebSocket('ws://8.134.249.145:16688/ws'); // 替换为你的 WebSocket 服务器地址
|
||||||
|
//ws.value = new WebSocket('ws://localhost:16688/ws'); // 替换为你的 WebSocket 服务器地址
|
||||||
|
|
||||||
|
ws.value.onopen = () => {
|
||||||
|
console.log('WebSocket 连接已打开');
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.value.onmessage = (event) => {
|
||||||
|
if(data.enableDanmu){
|
||||||
|
danmus.value.push(event.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.value.onerror = (error) => {
|
||||||
|
console.error('WebSocket 错误:', error);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.value.onclose = () => {
|
||||||
|
console.log('WebSocket 连接已关闭');
|
||||||
|
};
|
||||||
|
|
||||||
|
ticker.value=setInterval(() => {
|
||||||
|
GetFollowedFund().then(result => {
|
||||||
|
followList.value = result
|
||||||
|
console.log("followList",followList.value)
|
||||||
|
})
|
||||||
|
}, 1000*60)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearInterval(ticker.value)
|
||||||
|
ws.value.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function SendDanmu(){
|
||||||
|
ws.value.send(data.name)
|
||||||
|
}
|
||||||
|
function AddFund(){
|
||||||
|
FollowFund(data.code).then(result=>{
|
||||||
|
if(result){
|
||||||
|
message.success("关注成功")
|
||||||
|
GetFollowedFund().then(result => {
|
||||||
|
followList.value = result
|
||||||
|
console.log("followList",followList.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function unFollow(code){
|
||||||
|
UnFollowFund(code).then(result=>{
|
||||||
|
if(result){
|
||||||
|
message.success("取消关注成功")
|
||||||
|
GetFollowedFund().then(result => {
|
||||||
|
followList.value = result
|
||||||
|
console.log("followList",followList.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFundList(value){
|
||||||
|
GetfundList(value).then(result=>{
|
||||||
|
options.value=[]
|
||||||
|
result.forEach(item=>{
|
||||||
|
options.value.push({
|
||||||
|
label: item.name,
|
||||||
|
value: item.code,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function onSelectFund(value){
|
||||||
|
data.code=value
|
||||||
|
}
|
||||||
|
function formatterTitle(title){
|
||||||
|
return () => h(NEllipsis,{
|
||||||
|
style: {
|
||||||
|
'font-size': '16px',
|
||||||
|
'max-width': '180px',
|
||||||
|
},
|
||||||
|
},{default: () => title,}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<vue-danmaku v-model:danmus="danmus" style="height:100px; width:100%;z-index: 9;position:absolute; top: 400px; pointer-events: none;" ></vue-danmaku>
|
||||||
|
<n-flex justify="start" >
|
||||||
|
<n-grid :x-gap="8" :cols="3" :y-gap="8" >
|
||||||
|
<n-gi v-for="info in followList" style="margin-left: 2px" onmouseover="this.style.border='1px solid #3498db' " onmouseout="this.style.border='0px'">
|
||||||
|
<n-card :title="formatterTitle(info.name)">
|
||||||
|
<template #header-extra>
|
||||||
|
<n-tag size="small" :bordered="false" type="info">{{info.code}}</n-tag>
|
||||||
|
<n-tag size="small" :bordered="false" type="success" @click="unFollow(info.code)"> 取消关注</n-tag>
|
||||||
|
</template>
|
||||||
|
<n-tag size="small" :type="info.netEstimatedUnit>0?'error':'success'" :bordered="false" v-if="info.netEstimatedUnit">估算净值:{{info.netEstimatedUnit}}({{info.netEstimatedUnitTime}})</n-tag>
|
||||||
|
<n-divider vertical></n-divider>
|
||||||
|
<n-tag size="small" :type="info.netUnitValue>0?'error':'success'" :bordered="false" v-if="info.netUnitValue">单位净值:{{info.netUnitValue}}({{info.netUnitValueDate}})</n-tag>
|
||||||
|
<n-divider v-if="info.netUnitValue"></n-divider>
|
||||||
|
<n-flex justify="start">
|
||||||
|
<n-tag size="small" :type="info.fundBasic.netGrowth1>0?'error':'success'" :bordered="false" v-if="info.fundBasic.netGrowth1">近一月:{{info.fundBasic.netGrowth1}}%</n-tag>
|
||||||
|
<n-tag size="small" :type="info.fundBasic.netGrowth3>0?'error':'success'" :bordered="false" v-if="info.fundBasic.netGrowth3">近三月:{{info.fundBasic.netGrowth3}}%</n-tag>
|
||||||
|
<n-tag size="small" :type="info.fundBasic.netGrowth6>0?'error':'success'" :bordered="false" v-if="info.fundBasic.netGrowth6">近六月:{{info.fundBasic.netGrowth6}}%</n-tag>
|
||||||
|
<n-tag size="small" :type="info.fundBasic.netGrowth12>0?'error':'success'" :bordered="false" v-if="info.fundBasic.netGrowth12">近一年:{{info.fundBasic.netGrowth12}}%</n-tag>
|
||||||
|
<n-tag size="small" :type="info.fundBasic.netGrowth36>0?'error':'success'" :bordered="false" v-if="info.fundBasic.netGrowth36">近三年:{{info.fundBasic.netGrowth36}}%</n-tag>
|
||||||
|
<n-tag size="small" :type="info.fundBasic.netGrowth60>0?'error':'success'" :bordered="false" v-if="info.fundBasic.netGrowth60">近五年:{{info.fundBasic.netGrowth60}}%</n-tag>
|
||||||
|
<n-tag size="small" :type="info.fundBasic.netGrowthYTD>0?'error':'success'" :bordered="false" v-if="info.fundBasic.netGrowthYTD" >今年来:{{info.fundBasic.netGrowthYTD}}%</n-tag>
|
||||||
|
<n-tag size="small" :type="info.fundBasic.netGrowthAll>0?'error':'success'" :bordered="false" >成立来:{{info.fundBasic.netGrowthAll}}%</n-tag>
|
||||||
|
</n-flex>
|
||||||
|
<template #footer>
|
||||||
|
<n-flex justify="start">
|
||||||
|
<n-tag size="small" :bordered="false" type="warning"> {{info.fundBasic.type}}</n-tag>
|
||||||
|
<n-tag size="small" :bordered="false" type="info"> {{info.fundBasic.company}}:{{info.fundBasic.manager}}</n-tag>
|
||||||
|
</n-flex>
|
||||||
|
</template>
|
||||||
|
<!-- <template #action>
|
||||||
|
<n-flex justify="end">
|
||||||
|
<n-button size="tiny" type="warning" @click="unFollow(info.code)">取消关注</n-button>
|
||||||
|
</n-flex>
|
||||||
|
</template>-->
|
||||||
|
</n-card>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
</n-flex>
|
||||||
|
<div style="position: fixed;bottom: 18px;right:0;z-index: 10;width: 480px">
|
||||||
|
<n-input-group >
|
||||||
|
<n-auto-complete v-model:value="data.name"
|
||||||
|
:input-props="{
|
||||||
|
autocomplete: 'disabled',
|
||||||
|
}"
|
||||||
|
:options="options"
|
||||||
|
placeholder="基金名称/代码/弹幕"
|
||||||
|
clearable @update-value="getFundList" :on-select="onSelectFund"/>
|
||||||
|
<n-button type="primary" @click="AddFund">
|
||||||
|
<n-icon :component="Add"/> 关注
|
||||||
|
</n-button>
|
||||||
|
<n-button type="error" @click="SendDanmu" v-if="data.enableDanmu">
|
||||||
|
<n-icon :component="ChatboxOutline"/> 发送弹幕
|
||||||
|
</n-button>
|
||||||
|
</n-input-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -394,7 +394,7 @@ function getStockList(value){
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function updateData(result) {
|
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){
|
if(result["当前价格"]<=0){
|
||||||
result["当前价格"]=result["卖一报价"]
|
result["当前价格"]=result["卖一报价"]
|
||||||
@ -842,7 +842,7 @@ function share(code,name){
|
|||||||
</n-card >
|
</n-card >
|
||||||
</n-gi>
|
</n-gi>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
<div style="position: fixed;bottom: 18px;right:0;z-index: 10;width: 420px">
|
<div style="position: fixed;bottom: 18px;right:0;z-index: 10;width: 460px">
|
||||||
<!-- <n-card :bordered="false">-->
|
<!-- <n-card :bordered="false">-->
|
||||||
<n-input-group >
|
<n-input-group >
|
||||||
<!-- <n-button type="error" @click="addBTN=!addBTN" > <n-icon :component="Search"/> <n-text v-if="addBTN">隐藏</n-text></n-button>-->
|
<!-- <n-button type="error" @click="addBTN=!addBTN" > <n-icon :component="Search"/> <n-text v-if="addBTN">隐藏</n-text></n-button>-->
|
||||||
@ -855,7 +855,7 @@ function share(code,name){
|
|||||||
placeholder="股票指数名称/代码/弹幕"
|
placeholder="股票指数名称/代码/弹幕"
|
||||||
clearable @update-value="getStockList" :on-select="onSelect"/>
|
clearable @update-value="getStockList" :on-select="onSelect"/>
|
||||||
<n-button type="primary" @click="AddStock" v-if="addBTN">
|
<n-button type="primary" @click="AddStock" v-if="addBTN">
|
||||||
<n-icon :component="Add"/> 关注该股票
|
<n-icon :component="Add"/> 关注
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button type="error" @click="SendDanmu" v-if="data.enableDanmu">
|
<n-button type="error" @click="SendDanmu" v-if="data.enableDanmu">
|
||||||
<n-icon :component="ChatboxOutline"/> 发送弹幕
|
<n-icon :component="ChatboxOutline"/> 发送弹幕
|
||||||
|
@ -3,9 +3,11 @@ import { createMemoryHistory, createRouter } from 'vue-router'
|
|||||||
import stockView from '../components/stock.vue'
|
import stockView from '../components/stock.vue'
|
||||||
import settingsView from '../components/settings.vue'
|
import settingsView from '../components/settings.vue'
|
||||||
import about from "../components/about.vue";
|
import about from "../components/about.vue";
|
||||||
|
import fundView from "../components/fund.vue";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ path: '/', component: stockView,name: 'stock' },
|
{ path: '/', component: stockView,name: 'stock' },
|
||||||
|
{ path: '/fund', component: fundView,name: 'fund' },
|
||||||
{ path: '/settings/:id', component: settingsView,name: 'settings' },
|
{ path: '/settings/:id', component: settingsView,name: 'settings' },
|
||||||
{ path: '/about', component: about,name: 'about' },
|
{ path: '/about', component: about,name: 'about' },
|
||||||
]
|
]
|
||||||
|
8
frontend/wailsjs/go/main/App.d.ts
vendored
8
frontend/wailsjs/go/main/App.d.ts
vendored
@ -9,16 +9,22 @@ export function ExportConfig():Promise<string>;
|
|||||||
|
|
||||||
export function Follow(arg1:string):Promise<string>;
|
export function Follow(arg1:string):Promise<string>;
|
||||||
|
|
||||||
|
export function FollowFund(arg1:string):Promise<string>;
|
||||||
|
|
||||||
export function GetAIResponseResult(arg1:string):Promise<models.AIResponseResult>;
|
export function GetAIResponseResult(arg1:string):Promise<models.AIResponseResult>;
|
||||||
|
|
||||||
export function GetConfig():Promise<data.Settings>;
|
export function GetConfig():Promise<data.Settings>;
|
||||||
|
|
||||||
export function GetFollowList():Promise<any>;
|
export function GetFollowList():Promise<any>;
|
||||||
|
|
||||||
|
export function GetFollowedFund():Promise<Array<data.FollowedFund>>;
|
||||||
|
|
||||||
export function GetStockList(arg1:string):Promise<Array<data.StockBasic>>;
|
export function GetStockList(arg1:string):Promise<Array<data.StockBasic>>;
|
||||||
|
|
||||||
export function GetVersionInfo():Promise<models.VersionInfo>;
|
export function GetVersionInfo():Promise<models.VersionInfo>;
|
||||||
|
|
||||||
|
export function GetfundList(arg1:string):Promise<Array<data.FundBasic>>;
|
||||||
|
|
||||||
export function Greet(arg1:string):Promise<data.StockInfo>;
|
export function Greet(arg1:string):Promise<data.StockInfo>;
|
||||||
|
|
||||||
export function NewChatStream(arg1:string,arg2:string,arg3:string):Promise<void>;
|
export function NewChatStream(arg1:string,arg2:string,arg3:string):Promise<void>;
|
||||||
@ -39,4 +45,6 @@ export function ShareAnalysis(arg1:string,arg2:string):Promise<string>;
|
|||||||
|
|
||||||
export function UnFollow(arg1:string):Promise<string>;
|
export function UnFollow(arg1:string):Promise<string>;
|
||||||
|
|
||||||
|
export function UnFollowFund(arg1:string):Promise<string>;
|
||||||
|
|
||||||
export function UpdateConfig(arg1:data.Settings):Promise<string>;
|
export function UpdateConfig(arg1:data.Settings):Promise<string>;
|
||||||
|
@ -14,6 +14,10 @@ export function Follow(arg1) {
|
|||||||
return window['go']['main']['App']['Follow'](arg1);
|
return window['go']['main']['App']['Follow'](arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function FollowFund(arg1) {
|
||||||
|
return window['go']['main']['App']['FollowFund'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
export function GetAIResponseResult(arg1) {
|
export function GetAIResponseResult(arg1) {
|
||||||
return window['go']['main']['App']['GetAIResponseResult'](arg1);
|
return window['go']['main']['App']['GetAIResponseResult'](arg1);
|
||||||
}
|
}
|
||||||
@ -26,6 +30,10 @@ export function GetFollowList() {
|
|||||||
return window['go']['main']['App']['GetFollowList']();
|
return window['go']['main']['App']['GetFollowList']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetFollowedFund() {
|
||||||
|
return window['go']['main']['App']['GetFollowedFund']();
|
||||||
|
}
|
||||||
|
|
||||||
export function GetStockList(arg1) {
|
export function GetStockList(arg1) {
|
||||||
return window['go']['main']['App']['GetStockList'](arg1);
|
return window['go']['main']['App']['GetStockList'](arg1);
|
||||||
}
|
}
|
||||||
@ -34,6 +42,10 @@ export function GetVersionInfo() {
|
|||||||
return window['go']['main']['App']['GetVersionInfo']();
|
return window['go']['main']['App']['GetVersionInfo']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetfundList(arg1) {
|
||||||
|
return window['go']['main']['App']['GetfundList'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
export function Greet(arg1) {
|
export function Greet(arg1) {
|
||||||
return window['go']['main']['App']['Greet'](arg1);
|
return window['go']['main']['App']['Greet'](arg1);
|
||||||
}
|
}
|
||||||
@ -74,6 +86,10 @@ export function UnFollow(arg1) {
|
|||||||
return window['go']['main']['App']['UnFollow'](arg1);
|
return window['go']['main']['App']['UnFollow'](arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function UnFollowFund(arg1) {
|
||||||
|
return window['go']['main']['App']['UnFollowFund'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
export function UpdateConfig(arg1) {
|
export function UpdateConfig(arg1) {
|
||||||
return window['go']['main']['App']['UpdateConfig'](arg1);
|
return window['go']['main']['App']['UpdateConfig'](arg1);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,146 @@
|
|||||||
export namespace data {
|
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 {
|
export class Settings {
|
||||||
ID: number;
|
ID: number;
|
||||||
// Go type: time
|
// Go type: time
|
||||||
|
2
main.go
2
main.go
@ -63,6 +63,8 @@ func main() {
|
|||||||
db.Dao.AutoMigrate(&models.AIResponseResult{})
|
db.Dao.AutoMigrate(&models.AIResponseResult{})
|
||||||
db.Dao.AutoMigrate(&models.StockInfoHK{})
|
db.Dao.AutoMigrate(&models.StockInfoHK{})
|
||||||
db.Dao.AutoMigrate(&models.StockInfoUS{})
|
db.Dao.AutoMigrate(&models.StockInfoUS{})
|
||||||
|
db.Dao.AutoMigrate(&data.FollowedFund{})
|
||||||
|
db.Dao.AutoMigrate(&data.FundBasic{})
|
||||||
|
|
||||||
if stocksBin != nil && len(stocksBin) > 0 {
|
if stocksBin != nil && len(stocksBin) > 0 {
|
||||||
go initStockData()
|
go initStockData()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user