Compare commits
61 Commits
1.0.0
...
v2025.1.9.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88fb3ce94c | ||
|
|
60d8efc158 | ||
|
|
a41ab5499a | ||
|
|
a54f769ea2 | ||
|
|
9a46788339 | ||
|
|
def92ad722 | ||
|
|
7e27996f17 | ||
|
|
ad428f83f8 | ||
|
|
d3c6c1d570 | ||
|
|
1554d3309d | ||
|
|
e7560f3e9b | ||
|
|
daa29b37a5 | ||
|
|
9a41560bee | ||
|
|
975ad611df | ||
|
|
b764770729 | ||
|
|
88aa793774 | ||
|
|
180bec8866 | ||
|
|
420d5b60f1 | ||
|
|
af1bc685a7 | ||
|
|
b0922b0878 | ||
|
|
200a160acf | ||
|
|
9fae9fc034 | ||
|
|
9a3393bfc3 | ||
|
|
64270d5df2 | ||
|
|
e808ca47b6 | ||
|
|
1b3c043ce6 | ||
|
|
04446d7521 | ||
|
|
2306a8e225 | ||
|
|
46aff404d4 | ||
|
|
1e5d9bc469 | ||
|
|
98844ce717 | ||
|
|
2bd91c6555 | ||
|
|
2166b0a39b | ||
|
|
2f2b19f5d7 | ||
|
|
685a7d23b2 | ||
|
|
5f1eaf02c4 | ||
|
|
116dae19cf | ||
|
|
a35b42f831 | ||
|
|
513cd69e3e | ||
|
|
0ce01bcdf0 | ||
|
|
afe5474264 | ||
|
|
f35847823b | ||
|
|
15120c98da | ||
|
|
6903abd6f8 | ||
|
|
f9a0c8d94e | ||
|
|
0c04272153 | ||
|
|
cf7e8415e6 | ||
|
|
1ab6875790 | ||
|
|
cc40da8371 | ||
|
|
29158f51af | ||
|
|
9665462ae5 | ||
|
|
71e3953cd8 | ||
|
|
bedeaad1f2 | ||
|
|
4f05820e2e | ||
|
|
abf05aabd8 | ||
|
|
9016289e96 | ||
|
|
bab53711b7 | ||
|
|
de67f07f41 | ||
|
|
869223cfb9 | ||
|
|
d240239fcc | ||
|
|
6b62385cad |
5
.github/workflows/main.yml
vendored
@@ -17,9 +17,12 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build:
|
||||
- name: 'go-stock'
|
||||
- name: 'go-stock-windows-amd64.exe'
|
||||
platform: 'windows/amd64'
|
||||
os: 'windows-latest'
|
||||
- name: 'go-stock-linux-amd64'
|
||||
platform: 'linux/amd64'
|
||||
os: 'ubuntu-latest'
|
||||
|
||||
runs-on: ${{ matrix.build.os }}
|
||||
steps:
|
||||
|
||||
5
.gitignore
vendored
@@ -107,5 +107,6 @@ dist
|
||||
|
||||
.idea/
|
||||
data/*.db
|
||||
build/*.exe
|
||||
/build/bin/go-stock-dev.exe
|
||||
./build/*.exe
|
||||
./build/bin/go-stock-dev.exe
|
||||
./build/bin/go-stock.exe
|
||||
|
||||
15
README.md
@@ -1,8 +1,19 @@
|
||||
# README
|
||||
|
||||
# Go fuck chinese stock market !!!
|
||||

|
||||
|
||||
|
||||
## Snapshot
|
||||

|
||||
### 成本仓位设置
|
||||

|
||||
### 日K
|
||||

|
||||
### 分时
|
||||

|
||||
### 钉钉报警通知
|
||||

|
||||
|
||||
|
||||
## About
|
||||
|
||||
A China stock data viewer build by [Wails](https://wails.io/) with [NavieUI](https://www.naiveui.com/).
|
||||
|
||||
343
app.go
@@ -1,46 +1,254 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/coocood/freecache"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/duke-git/lancet/v2/mathutil"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
"github.com/getlantern/systray"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"go-stock/backend/data"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"time"
|
||||
)
|
||||
|
||||
// App struct
|
||||
type App struct {
|
||||
ctx context.Context
|
||||
ctx context.Context
|
||||
cache *freecache.Cache
|
||||
}
|
||||
|
||||
// NewApp creates a new App application struct
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
cacheSize := 512 * 1024
|
||||
cache := freecache.NewCache(cacheSize)
|
||||
return &App{
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
// startup is called at application startup
|
||||
func (a *App) startup(ctx context.Context) {
|
||||
// Perform your setup here
|
||||
a.ctx = ctx
|
||||
|
||||
// 创建系统托盘
|
||||
go systray.Run(func() {
|
||||
onReady(a)
|
||||
}, func() {
|
||||
onExit(a)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// domReady is called after front-end resources have been loaded
|
||||
func (a App) domReady(ctx context.Context) {
|
||||
func (a *App) domReady(ctx context.Context) {
|
||||
// Add your action here
|
||||
//定时更新数据
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second * 1)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
if isTradingTime(time.Now()) {
|
||||
MonitorStockPrices(a)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// isTradingDay 判断是否是交易日
|
||||
func isTradingDay(date time.Time) bool {
|
||||
weekday := date.Weekday()
|
||||
// 判断是否是周末
|
||||
if weekday == time.Saturday || weekday == time.Sunday {
|
||||
return false
|
||||
}
|
||||
// 这里可以添加具体的节假日判断逻辑
|
||||
// 例如:判断是否是春节、国庆节等
|
||||
return true
|
||||
}
|
||||
|
||||
// isTradingTime 判断是否是交易时间
|
||||
func isTradingTime(date time.Time) bool {
|
||||
if !isTradingDay(date) {
|
||||
return false
|
||||
}
|
||||
|
||||
hour, minute, _ := date.Clock()
|
||||
|
||||
// 判断是否在9:15到11:30之间
|
||||
if (hour == 9 && minute >= 15) || (hour == 10) || (hour == 11 && minute <= 30) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 判断是否在13:00到15:00之间
|
||||
if (hour == 13) || (hour == 14) || (hour == 15 && minute <= 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func MonitorStockPrices(a *App) {
|
||||
dest := &[]data.FollowedStock{}
|
||||
db.Dao.Model(&data.FollowedStock{}).Find(dest)
|
||||
total := float64(0)
|
||||
//for _, follow := range *dest {
|
||||
// stockData := getStockInfo(follow)
|
||||
// total += stockData.ProfitAmountToday
|
||||
// price, _ := convertor.ToFloat(stockData.Price)
|
||||
// if stockData.PrePrice != price {
|
||||
// go runtime.EventsEmit(a.ctx, "stock_price", stockData)
|
||||
// }
|
||||
//}
|
||||
|
||||
stockInfos := GetStockInfos(*dest...)
|
||||
for _, stockInfo := range *stockInfos {
|
||||
total += stockInfo.ProfitAmountToday
|
||||
price, _ := convertor.ToFloat(stockInfo.Price)
|
||||
if stockInfo.PrePrice != price {
|
||||
go runtime.EventsEmit(a.ctx, "stock_price", stockInfo)
|
||||
}
|
||||
}
|
||||
|
||||
title := "go-stock " + time.Now().Format(time.DateTime) + fmt.Sprintf(" %.2f¥", total)
|
||||
runtime.WindowSetTitle(a.ctx, title)
|
||||
systray.SetTooltip(title)
|
||||
|
||||
}
|
||||
func GetStockInfos(follows ...data.FollowedStock) *[]data.StockInfo {
|
||||
stockCodes := make([]string, 0)
|
||||
for _, follow := range follows {
|
||||
stockCodes = append(stockCodes, follow.StockCode)
|
||||
}
|
||||
stockData, err := data.NewStockDataApi().GetStockCodeRealTimeData(stockCodes...)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("get stock code real time data error:%s", err.Error())
|
||||
return nil
|
||||
}
|
||||
stockInfos := make([]data.StockInfo, 0)
|
||||
for _, info := range *stockData {
|
||||
v, ok := slice.FindBy(follows, func(idx int, follow data.FollowedStock) bool {
|
||||
return follow.StockCode == info.Code
|
||||
})
|
||||
if ok {
|
||||
addStockFollowData(v, &info)
|
||||
stockInfos = append(stockInfos, info)
|
||||
}
|
||||
}
|
||||
return &stockInfos
|
||||
}
|
||||
func getStockInfo(follow data.FollowedStock) *data.StockInfo {
|
||||
stockCode := follow.StockCode
|
||||
stockDatas, err := data.NewStockDataApi().GetStockCodeRealTimeData(stockCode)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("get stock code real time data error:%s", err.Error())
|
||||
return nil
|
||||
}
|
||||
stockData := (*stockDatas)[0]
|
||||
addStockFollowData(follow, &stockData)
|
||||
return &stockData
|
||||
}
|
||||
|
||||
func addStockFollowData(follow data.FollowedStock, stockData *data.StockInfo) {
|
||||
stockData.PrePrice = follow.Price //上次当前价格
|
||||
stockData.Sort = follow.Sort
|
||||
stockData.CostPrice = follow.CostPrice //成本价
|
||||
stockData.CostVolume = follow.Volume //成本量
|
||||
stockData.AlarmChangePercent = follow.AlarmChangePercent
|
||||
stockData.AlarmPrice = follow.AlarmPrice
|
||||
|
||||
//当前价格
|
||||
price, _ := convertor.ToFloat(stockData.Price)
|
||||
//当前价格为0 时 使用卖一价格作为当前价格
|
||||
if price == 0 {
|
||||
price, _ = convertor.ToFloat(stockData.A1P)
|
||||
}
|
||||
//当前价格依然为0 时 使用买一报价作为当前价格
|
||||
if price == 0 {
|
||||
price, _ = convertor.ToFloat(stockData.B1P)
|
||||
}
|
||||
|
||||
//昨日收盘价
|
||||
preClosePrice, _ := convertor.ToFloat(stockData.PreClose)
|
||||
|
||||
//今日最高价
|
||||
highPrice, _ := convertor.ToFloat(stockData.High)
|
||||
if highPrice == 0 {
|
||||
highPrice, _ = convertor.ToFloat(stockData.Open)
|
||||
}
|
||||
|
||||
//今日最低价
|
||||
lowPrice, _ := convertor.ToFloat(stockData.Low)
|
||||
if lowPrice == 0 {
|
||||
lowPrice, _ = convertor.ToFloat(stockData.Open)
|
||||
}
|
||||
//开盘价
|
||||
//openPrice, _ := convertor.ToFloat(stockData.Open)
|
||||
|
||||
stockData.ChangePrice = mathutil.RoundToFloat(price-preClosePrice, 2)
|
||||
stockData.ChangePercent = mathutil.RoundToFloat(mathutil.Div(price-preClosePrice, preClosePrice)*100, 3)
|
||||
stockData.HighRate = mathutil.RoundToFloat(mathutil.Div(highPrice-preClosePrice, preClosePrice)*100, 3)
|
||||
stockData.LowRate = mathutil.RoundToFloat(mathutil.Div(lowPrice-preClosePrice, preClosePrice)*100, 3)
|
||||
if follow.CostPrice > 0 && follow.Volume > 0 {
|
||||
stockData.Profit = mathutil.RoundToFloat(mathutil.Div(price-follow.CostPrice, follow.CostPrice)*100, 3)
|
||||
stockData.ProfitAmount = mathutil.RoundToFloat((price-follow.CostPrice)*float64(follow.Volume), 2)
|
||||
stockData.ProfitAmountToday = mathutil.RoundToFloat((price-preClosePrice)*float64(follow.Volume), 2)
|
||||
}
|
||||
|
||||
//logger.SugaredLogger.Debugf("stockData:%+v", stockData)
|
||||
if follow.Price != price {
|
||||
go db.Dao.Model(follow).Where("stock_code = ?", follow.StockCode).Updates(map[string]interface{}{
|
||||
"price": stockData.Price,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// beforeClose is called when the application is about to quit,
|
||||
// either by clicking the window close button or calling runtime.Quit.
|
||||
// Returning true will cause the application to continue, false will continue shutdown as normal.
|
||||
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
|
||||
|
||||
dialog, err := runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
|
||||
Type: runtime.QuestionDialog,
|
||||
Title: "go-stock",
|
||||
Message: "确定关闭吗?",
|
||||
Buttons: []string{"确定"},
|
||||
Icon: icon,
|
||||
CancelButton: "取消",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("dialog error:%s", err.Error())
|
||||
return false
|
||||
}
|
||||
logger.SugaredLogger.Debugf("dialog:%s", dialog)
|
||||
if dialog == "No" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// shutdown is called at application termination
|
||||
func (a *App) shutdown(ctx context.Context) {
|
||||
// Perform your teardown here
|
||||
systray.Quit()
|
||||
}
|
||||
|
||||
// Greet returns a greeting for the given name
|
||||
func (a *App) Greet(name string) *data.StockInfo {
|
||||
stockInfo, _ := data.NewStockDataApi().GetStockCodeRealTimeData(name)
|
||||
func (a *App) Greet(stockCode string) *data.StockInfo {
|
||||
//stockInfo, _ := data.NewStockDataApi().GetStockCodeRealTimeData(stockCode)
|
||||
|
||||
follow := &data.FollowedStock{
|
||||
StockCode: stockCode,
|
||||
}
|
||||
db.Dao.Model(follow).Where("stock_code = ?", stockCode).First(follow)
|
||||
stockInfo := getStockInfo(*follow)
|
||||
return stockInfo
|
||||
}
|
||||
|
||||
@@ -63,3 +271,128 @@ func (a *App) GetStockList(key string) []data.StockBasic {
|
||||
func (a *App) SetCostPriceAndVolume(stockCode string, price float64, volume int64) string {
|
||||
return data.NewStockDataApi().SetCostPriceAndVolume(price, volume, stockCode)
|
||||
}
|
||||
|
||||
func (a *App) SetAlarmChangePercent(val, alarmPrice float64, stockCode string) string {
|
||||
return data.NewStockDataApi().SetAlarmChangePercent(val, alarmPrice, stockCode)
|
||||
}
|
||||
func (a *App) SetStockSort(sort int64, stockCode string) {
|
||||
data.NewStockDataApi().SetStockSort(sort, stockCode)
|
||||
}
|
||||
func (a *App) SendDingDingMessage(message string, stockCode string) string {
|
||||
ttl, _ := a.cache.TTL([]byte(stockCode))
|
||||
logger.SugaredLogger.Infof("stockCode %s ttl:%d", stockCode, ttl)
|
||||
if ttl > 0 {
|
||||
return ""
|
||||
}
|
||||
err := a.cache.Set([]byte(stockCode), []byte("1"), 60*5)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("set cache error:%s", err.Error())
|
||||
return ""
|
||||
}
|
||||
return data.NewDingDingAPI().SendDingDingMessage(message)
|
||||
}
|
||||
|
||||
// SendDingDingMessageByType msgType 报警类型: 1 涨跌报警;2 股价报警 3 成本价报警
|
||||
func (a *App) SendDingDingMessageByType(message string, stockCode string, msgType int) string {
|
||||
ttl, _ := a.cache.TTL([]byte(stockCode))
|
||||
logger.SugaredLogger.Infof("stockCode %s ttl:%d", stockCode, ttl)
|
||||
if ttl > 0 {
|
||||
return ""
|
||||
}
|
||||
err := a.cache.Set([]byte(stockCode), []byte("1"), getMsgTypeTTL(msgType))
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("set cache error:%s", err.Error())
|
||||
return ""
|
||||
}
|
||||
stockInfo := &data.StockInfo{}
|
||||
db.Dao.Model(stockInfo).Where("code = ?", stockCode).First(stockInfo)
|
||||
if !data.NewAlertWindowsApi("go-stock消息通知", getMsgTypeName(msgType), GenNotificationMsg(stockInfo), "").SendNotification() {
|
||||
return data.NewDingDingAPI().SendDingDingMessage(message)
|
||||
}
|
||||
return "发送系统消息成功"
|
||||
}
|
||||
|
||||
func GenNotificationMsg(stockInfo *data.StockInfo) string {
|
||||
Price, err := convertor.ToFloat(stockInfo.Price)
|
||||
if err != nil {
|
||||
Price = 0
|
||||
}
|
||||
PreClose, err := convertor.ToFloat(stockInfo.PreClose)
|
||||
if err != nil {
|
||||
PreClose = 0
|
||||
}
|
||||
var RF float64
|
||||
if PreClose > 0 {
|
||||
RF = mathutil.RoundToFloat(((Price-PreClose)/PreClose)*100, 2)
|
||||
}
|
||||
|
||||
return "[" + stockInfo.Name + "] " + stockInfo.Price + " " + convertor.ToString(RF) + "% " + stockInfo.Date + " " + stockInfo.Time
|
||||
}
|
||||
|
||||
// msgType : 1 涨跌报警(5分钟);2 股价报警(30分钟) 3 成本价报警(30分钟)
|
||||
func getMsgTypeTTL(msgType int) int {
|
||||
switch msgType {
|
||||
case 1:
|
||||
return 60 * 5
|
||||
case 2:
|
||||
return 60 * 30
|
||||
case 3:
|
||||
return 60 * 30
|
||||
default:
|
||||
return 60 * 5
|
||||
}
|
||||
}
|
||||
|
||||
func getMsgTypeName(msgType int) string {
|
||||
switch msgType {
|
||||
case 1:
|
||||
return "涨跌报警"
|
||||
case 2:
|
||||
return "股价报警"
|
||||
case 3:
|
||||
return "成本价报警"
|
||||
default:
|
||||
return "未知类型"
|
||||
}
|
||||
}
|
||||
|
||||
func onExit(a *App) {
|
||||
// 清理操作
|
||||
logger.SugaredLogger.Infof("onExit")
|
||||
runtime.Quit(a.ctx)
|
||||
}
|
||||
|
||||
func onReady(a *App) {
|
||||
|
||||
// 初始化操作
|
||||
logger.SugaredLogger.Infof("onReady")
|
||||
systray.SetIcon(icon2)
|
||||
systray.SetTitle("go-stock")
|
||||
systray.SetTooltip("go-stock 股票行情实时获取")
|
||||
|
||||
// 创建菜单项
|
||||
show := systray.AddMenuItem("显示", "显示应用程序")
|
||||
//hide := systray.AddMenuItem("隐藏", "隐藏应用程序")
|
||||
systray.AddSeparator()
|
||||
mQuitOrig := systray.AddMenuItem("退出", "退出应用程序")
|
||||
|
||||
// 监听菜单项点击事件
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-mQuitOrig.ClickedCh:
|
||||
logger.SugaredLogger.Infof("退出应用程序")
|
||||
runtime.Quit(a.ctx)
|
||||
//systray.Quit()
|
||||
case <-show.ClickedCh:
|
||||
logger.SugaredLogger.Infof("显示应用程序")
|
||||
runtime.WindowShow(a.ctx)
|
||||
//runtime.WindowShow(a.ctx)
|
||||
//case <-hide.ClickedCh:
|
||||
// logger.SugaredLogger.Infof("隐藏应用程序")
|
||||
// runtime.Hide(a.ctx)
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
186
app_linux.go
Normal file
@@ -0,0 +1,186 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/coocood/freecache"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/duke-git/lancet/v2/mathutil"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"go-stock/backend/data"
|
||||
"go-stock/backend/logger"
|
||||
)
|
||||
|
||||
// App struct
|
||||
type App struct {
|
||||
ctx context.Context
|
||||
cache *freecache.Cache
|
||||
}
|
||||
|
||||
// NewApp creates a new App application struct
|
||||
func NewApp() *App {
|
||||
cacheSize := 512 * 1024
|
||||
cache := freecache.NewCache(cacheSize)
|
||||
return &App{
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
// startup is called at application startup
|
||||
func (a *App) startup(ctx context.Context) {
|
||||
// Perform your setup here
|
||||
a.ctx = ctx
|
||||
|
||||
}
|
||||
|
||||
// domReady is called after front-end resources have been loaded
|
||||
func (a *App) domReady(ctx context.Context) {
|
||||
// Add your action here
|
||||
//ticker := time.NewTicker(time.Second)
|
||||
//defer ticker.Stop()
|
||||
////定时更新数据
|
||||
//go func() {
|
||||
// for range ticker.C {
|
||||
// runtime.WindowSetTitle(ctx, "go-stock "+time.Now().Format("2006-01-02 15:04:05"))
|
||||
// }
|
||||
//}()
|
||||
}
|
||||
|
||||
// beforeClose is called when the application is about to quit,
|
||||
// either by clicking the window close button or calling runtime.Quit.
|
||||
// Returning true will cause the application to continue, false will continue shutdown as normal.
|
||||
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
|
||||
|
||||
dialog, err := runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
|
||||
Type: runtime.QuestionDialog,
|
||||
Title: "go-stock",
|
||||
Message: "确定关闭吗?",
|
||||
Buttons: []string{"确定"},
|
||||
Icon: icon,
|
||||
CancelButton: "取消",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
logger.SugaredLogger.Debugf("dialog:%s", dialog)
|
||||
if dialog == "No" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// shutdown is called at application termination
|
||||
func (a *App) shutdown(ctx context.Context) {
|
||||
// Perform your teardown here
|
||||
}
|
||||
|
||||
// Greet returns a greeting for the given name
|
||||
func (a *App) Greet(name string) *data.StockInfo {
|
||||
stockDatas, _ := data.NewStockDataApi().GetStockCodeRealTimeData(name)
|
||||
stockData := (*stockDatas)[0]
|
||||
return &stockData
|
||||
}
|
||||
|
||||
func (a *App) Follow(stockCode string) string {
|
||||
return data.NewStockDataApi().Follow(stockCode)
|
||||
}
|
||||
|
||||
func (a *App) UnFollow(stockCode string) string {
|
||||
return data.NewStockDataApi().UnFollow(stockCode)
|
||||
}
|
||||
|
||||
func (a *App) GetFollowList() []data.FollowedStock {
|
||||
return data.NewStockDataApi().GetFollowList()
|
||||
}
|
||||
|
||||
func (a *App) GetStockList(key string) []data.StockBasic {
|
||||
return data.NewStockDataApi().GetStockList(key)
|
||||
}
|
||||
|
||||
func (a *App) SetCostPriceAndVolume(stockCode string, price float64, volume int64) string {
|
||||
return data.NewStockDataApi().SetCostPriceAndVolume(price, volume, stockCode)
|
||||
}
|
||||
|
||||
func (a *App) SetAlarmChangePercent(val, alarmPrice float64, stockCode string) string {
|
||||
return data.NewStockDataApi().SetAlarmChangePercent(val, alarmPrice, stockCode)
|
||||
}
|
||||
|
||||
func (a *App) SendDingDingMessage(message string, stockCode string) string {
|
||||
ttl, _ := a.cache.TTL([]byte(stockCode))
|
||||
logger.SugaredLogger.Infof("stockCode %s ttl:%d", stockCode, ttl)
|
||||
if ttl > 0 {
|
||||
return ""
|
||||
}
|
||||
err := a.cache.Set([]byte(stockCode), []byte("1"), 60*5)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("set cache error:%s", err.Error())
|
||||
return ""
|
||||
}
|
||||
return data.NewDingDingAPI().SendDingDingMessage(message)
|
||||
}
|
||||
|
||||
func (a *App) SetStockSort(sort int64, stockCode string) {
|
||||
data.NewStockDataApi().SetStockSort(sort, stockCode)
|
||||
}
|
||||
|
||||
// SendDingDingMessageByType msgType 报警类型: 1 涨跌报警;2 股价报警 3 成本价报警
|
||||
func (a *App) SendDingDingMessageByType(message string, stockCode string, msgType int) string {
|
||||
ttl, _ := a.cache.TTL([]byte(stockCode))
|
||||
logger.SugaredLogger.Infof("stockCode %s ttl:%d", stockCode, ttl)
|
||||
if ttl > 0 {
|
||||
return ""
|
||||
}
|
||||
err := a.cache.Set([]byte(stockCode), []byte("1"), getMsgTypeTTL(msgType))
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("set cache error:%s", err.Error())
|
||||
return ""
|
||||
}
|
||||
return data.NewDingDingAPI().SendDingDingMessage(message)
|
||||
}
|
||||
|
||||
func GenNotificationMsg(stockInfo *data.StockInfo) string {
|
||||
Price, err := convertor.ToFloat(stockInfo.Price)
|
||||
if err != nil {
|
||||
Price = 0
|
||||
}
|
||||
PreClose, err := convertor.ToFloat(stockInfo.PreClose)
|
||||
if err != nil {
|
||||
PreClose = 0
|
||||
}
|
||||
var RF float64
|
||||
if PreClose > 0 {
|
||||
RF = mathutil.RoundToFloat(((Price-PreClose)/PreClose)*100, 2)
|
||||
}
|
||||
|
||||
return "[" + stockInfo.Name + "] " + stockInfo.Price + " " + convertor.ToString(RF) + "% " + stockInfo.Date + " " + stockInfo.Time
|
||||
}
|
||||
|
||||
// msgType : 1 涨跌报警(5分钟);2 股价报警(30分钟) 3 成本价报警(30分钟)
|
||||
func getMsgTypeTTL(msgType int) int {
|
||||
switch msgType {
|
||||
case 1:
|
||||
return 60 * 5
|
||||
case 2:
|
||||
return 60 * 30
|
||||
case 3:
|
||||
return 60 * 30
|
||||
default:
|
||||
return 60 * 5
|
||||
}
|
||||
}
|
||||
|
||||
func getMsgTypeName(msgType int) string {
|
||||
switch msgType {
|
||||
case 1:
|
||||
return "涨跌报警"
|
||||
case 2:
|
||||
return "股价报警"
|
||||
case 3:
|
||||
return "成本价报警"
|
||||
default:
|
||||
return "未知类型"
|
||||
}
|
||||
}
|
||||
48
backend/data/alert_windows_api.go
Normal file
@@ -0,0 +1,48 @@
|
||||
//go:build windows
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"github.com/go-toast/toast"
|
||||
"go-stock/backend/logger"
|
||||
)
|
||||
|
||||
// AlertWindowsApi @Author spark
|
||||
// @Date 2025/1/8 9:40
|
||||
// @Desc
|
||||
// -----------------------------------------------------------------------------------
|
||||
type alertWindowsApi struct {
|
||||
AppID string
|
||||
// 窗口标题
|
||||
Title string
|
||||
// 窗口内容
|
||||
Content string
|
||||
// 窗口图标
|
||||
Icon string
|
||||
}
|
||||
|
||||
func NewAlertWindowsApi(AppID string, Title string, Content string, Icon string) *alertWindowsApi {
|
||||
return &alertWindowsApi{
|
||||
AppID: AppID,
|
||||
Title: Title,
|
||||
Content: Content,
|
||||
Icon: Icon,
|
||||
}
|
||||
}
|
||||
|
||||
func (a alertWindowsApi) SendNotification() bool {
|
||||
notification := toast.Notification{
|
||||
AppID: a.AppID,
|
||||
Title: a.Title,
|
||||
Message: a.Content,
|
||||
Icon: a.Icon,
|
||||
Duration: "short",
|
||||
Audio: toast.Default,
|
||||
}
|
||||
err := notification.Push()
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
30
backend/data/alert_windows_api_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
//go:build windows
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"github.com/go-toast/toast"
|
||||
"go-stock/backend/logger"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
// @Date 2025/1/8 9:40
|
||||
// @Desc
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
func TestAlert(t *testing.T) {
|
||||
notification := toast.Notification{
|
||||
AppID: "go-stock",
|
||||
Title: "Hello, World!",
|
||||
Message: "This is a toast notification.",
|
||||
Icon: "../../build/appicon.png",
|
||||
Duration: "short",
|
||||
Audio: toast.Default,
|
||||
}
|
||||
err := notification.Push()
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
77
backend/data/dingding_api.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"github.com/go-resty/resty/v2"
|
||||
"go-stock/backend/logger"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
// @Date 2025/1/3 13:53
|
||||
// @Desc
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
const dingding_robot_url = "https://oapi.dingtalk.com/robot/send?access_token=0237527b404598f37ae5d83ef36e936860c7ba5d3892cd43f64c4159d3ed7cb1"
|
||||
|
||||
type DingDingAPI struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewDingDingAPI() *DingDingAPI {
|
||||
return &DingDingAPI{
|
||||
client: resty.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (DingDingAPI) SendDingDingMessage(message string) string {
|
||||
// 发送钉钉消息
|
||||
resp, err := resty.New().R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(message).
|
||||
Post(dingding_robot_url)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return "发送钉钉消息失败"
|
||||
}
|
||||
logger.SugaredLogger.Infof("send dingding message: %s", resp.String())
|
||||
return "发送钉钉消息成功"
|
||||
}
|
||||
|
||||
func (DingDingAPI) SendToDingDing(title, message string) string {
|
||||
// 发送钉钉消息
|
||||
resp, err := resty.New().R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(&Message{
|
||||
Msgtype: "markdown",
|
||||
Markdown: Markdown{
|
||||
Title: "go-stock " + title,
|
||||
Text: message,
|
||||
},
|
||||
At: At{
|
||||
IsAtAll: true,
|
||||
},
|
||||
}).
|
||||
Post(dingding_robot_url)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return "发送钉钉消息失败"
|
||||
}
|
||||
logger.SugaredLogger.Infof("send dingding message: %s", resp.String())
|
||||
return "发送钉钉消息成功"
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Msgtype string `json:"msgtype"`
|
||||
Markdown Markdown `json:"markdown"`
|
||||
At At `json:"at"`
|
||||
}
|
||||
|
||||
type Markdown struct {
|
||||
Title string `json:"title"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type At struct {
|
||||
AtMobiles []string `json:"atMobiles"`
|
||||
AtUserIds []string `json:"atUserIds"`
|
||||
IsAtAll bool `json:"isAtAll"`
|
||||
}
|
||||
31
backend/data/dingding_api_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"github.com/go-resty/resty/v2"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
// @Date 2025/1/3 13:53
|
||||
// @Desc
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
func TestRobot(t *testing.T) {
|
||||
resp, err := resty.New().R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(`{
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title":"go-stock",
|
||||
"text": "#### 杭州天气 @150XXXXXXXX \n > 9度,西北风1级,空气良89,相对温度73%\n > \n > ###### 10点20分发布 [天气](https://www.dingtalk.com) \n"
|
||||
},
|
||||
"at": {
|
||||
"isAtAll": true
|
||||
}
|
||||
}`).
|
||||
Post(dingding_robot_url)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(resp.String())
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"go-stock/backend/db"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/plugin/soft_delete"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -31,39 +33,55 @@ type StockDataApi struct {
|
||||
}
|
||||
type StockInfo struct {
|
||||
gorm.Model
|
||||
Date string `json:"日期" gorm:"index"`
|
||||
Time string `json:"时间" gorm:"index"`
|
||||
Code string `json:"股票代码" gorm:"index"`
|
||||
Name string `json:"股票名称" gorm:"index"`
|
||||
Price string `json:"当前价格"`
|
||||
Volume string `json:"成交的股票数"`
|
||||
Amount string `json:"成交金额"`
|
||||
Open string `json:"今日开盘价"`
|
||||
PreClose string `json:"昨日收盘价"`
|
||||
High string `json:"今日最低价"`
|
||||
Low string `json:"今日最高价"`
|
||||
Bid string `json:"竞买价"`
|
||||
Ask string `json:"竞卖价"`
|
||||
B1P string `json:"买一报价"`
|
||||
B1V string `json:"买一申报"`
|
||||
B2P string `json:"买二报价"`
|
||||
B2V string `json:"买二申报"`
|
||||
B3P string `json:"买三报价"`
|
||||
B3V string `json:"买三申报"`
|
||||
B4P string `json:"买四报价"`
|
||||
B4V string `json:"买四申报"`
|
||||
B5P string `json:"买五报价"`
|
||||
B5V string `json:"买五申报"`
|
||||
A1P string `json:"卖一报价"`
|
||||
A1V string `json:"卖一申报"`
|
||||
A2P string `json:"卖二报价"`
|
||||
A2V string `json:"卖二申报"`
|
||||
A3P string `json:"卖三报价"`
|
||||
A3V string `json:"卖三申报"`
|
||||
A4P string `json:"卖四报价"`
|
||||
A4V string `json:"卖四申报"`
|
||||
A5P string `json:"卖五报价"`
|
||||
A5V string `json:"卖五申报"`
|
||||
Date string `json:"日期" gorm:"index"`
|
||||
Time string `json:"时间" gorm:"index"`
|
||||
Code string `json:"股票代码" gorm:"index"`
|
||||
Name string `json:"股票名称" gorm:"index"`
|
||||
PrePrice float64 `json:"上次当前价格"`
|
||||
Price string `json:"当前价格"`
|
||||
Volume string `json:"成交的股票数"`
|
||||
Amount string `json:"成交金额"`
|
||||
Open string `json:"今日开盘价"`
|
||||
PreClose string `json:"昨日收盘价"`
|
||||
High string `json:"今日最高价"`
|
||||
Low string `json:"今日最低价"`
|
||||
Bid string `json:"竞买价"`
|
||||
Ask string `json:"竞卖价"`
|
||||
B1P string `json:"买一报价"`
|
||||
B1V string `json:"买一申报"`
|
||||
B2P string `json:"买二报价"`
|
||||
B2V string `json:"买二申报"`
|
||||
B3P string `json:"买三报价"`
|
||||
B3V string `json:"买三申报"`
|
||||
B4P string `json:"买四报价"`
|
||||
B4V string `json:"买四申报"`
|
||||
B5P string `json:"买五报价"`
|
||||
B5V string `json:"买五申报"`
|
||||
A1P string `json:"卖一报价"`
|
||||
A1V string `json:"卖一申报"`
|
||||
A2P string `json:"卖二报价"`
|
||||
A2V string `json:"卖二申报"`
|
||||
A3P string `json:"卖三报价"`
|
||||
A3V string `json:"卖三申报"`
|
||||
A4P string `json:"卖四报价"`
|
||||
A4V string `json:"卖四申报"`
|
||||
A5P string `json:"卖五报价"`
|
||||
A5V string `json:"卖五申报"`
|
||||
|
||||
//以下是字段值需二次计算
|
||||
ChangePercent float64 `json:"changePercent"` //涨跌幅
|
||||
ChangePrice float64 `json:"changePrice"` //涨跌额
|
||||
HighRate float64 `json:"highRate"` //最高涨跌
|
||||
LowRate float64 `json:"lowRate"` //最低涨跌
|
||||
CostPrice float64 `json:"costPrice"` //成本价
|
||||
CostVolume int64 `json:"costVolume"` //持仓数量
|
||||
Profit float64 `json:"profit"` //总盈亏率
|
||||
ProfitAmount float64 `json:"profitAmount"` //总盈亏金额
|
||||
ProfitAmountToday float64 `json:"profitAmountToday"` //今日盈亏金额
|
||||
|
||||
Sort int64 `json:"sort"` //排序
|
||||
AlarmChangePercent float64 `json:"alarmChangePercent"`
|
||||
AlarmPrice float64 `json:"alarmPrice"`
|
||||
}
|
||||
|
||||
func (receiver StockInfo) TableName() string {
|
||||
@@ -125,15 +143,18 @@ type StockBasic struct {
|
||||
}
|
||||
|
||||
type FollowedStock struct {
|
||||
StockCode string
|
||||
Name string
|
||||
Volume int64
|
||||
CostPrice float64
|
||||
Price float64
|
||||
PriceChange float64
|
||||
ChangePercent float64
|
||||
Time time.Time
|
||||
Sort int64
|
||||
StockCode string
|
||||
Name string
|
||||
Volume int64
|
||||
CostPrice float64
|
||||
Price float64
|
||||
PriceChange float64
|
||||
ChangePercent float64
|
||||
AlarmChangePercent float64
|
||||
AlarmPrice float64
|
||||
Time time.Time
|
||||
Sort int64
|
||||
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
|
||||
}
|
||||
|
||||
func (receiver FollowedStock) TableName() string {
|
||||
@@ -160,15 +181,62 @@ func NewStockDataApi() *StockDataApi {
|
||||
client: resty.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetIndexBasic 获取指数信息
|
||||
func (receiver StockDataApi) GetIndexBasic() {
|
||||
res := &TushareStockBasicResponse{}
|
||||
fields := "ts_code,name,market,publisher,category,base_date,base_point,list_date,fullname,index_type,weight_rule,desc"
|
||||
_, err := receiver.client.R().
|
||||
SetHeader("content-type", "application/json").
|
||||
SetBody(&TushareRequest{
|
||||
ApiName: "index_basic",
|
||||
Token: TushareToken,
|
||||
Params: nil,
|
||||
Fields: fields}).
|
||||
SetResult(res).
|
||||
Post(tushare_api_url)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
if res.Code != 0 {
|
||||
logger.SugaredLogger.Error(res.Msg)
|
||||
return
|
||||
}
|
||||
//ioutil.WriteFile("index_basic.json", resp.Body(), 0666)
|
||||
|
||||
for _, item := range res.Data.Items {
|
||||
data := map[string]any{}
|
||||
for _, field := range strings.Split(fields, ",") {
|
||||
idx := slice.IndexOf(res.Data.Fields, field)
|
||||
if idx == -1 {
|
||||
continue
|
||||
}
|
||||
data[field] = item[idx]
|
||||
}
|
||||
index := &IndexBasic{}
|
||||
jsonData, _ := json.Marshal(data)
|
||||
err := json.Unmarshal(jsonData, index)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
db.Dao.Model(&IndexBasic{}).FirstOrCreate(index, &IndexBasic{TsCode: index.TsCode}).Where("ts_code = ?", index.TsCode).Updates(index)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// map转换为结构体
|
||||
|
||||
func (receiver StockDataApi) GetStockBaseInfo() {
|
||||
res := &TushareStockBasicResponse{}
|
||||
fields := "ts_code,symbol,name,area,industry,cnspell,market,list_date,act_name,act_ent_type,fullname,exchange,list_status,curr_type,enname,delist_date,is_hs"
|
||||
_, err := receiver.client.R().
|
||||
SetHeader("content-type", "application/json").
|
||||
SetBody(&TushareRequest{
|
||||
ApiName: "stock_basic",
|
||||
Token: TushareToken,
|
||||
Params: nil,
|
||||
Fields: "*",
|
||||
Fields: fields,
|
||||
}).
|
||||
SetResult(res).
|
||||
Post(tushare_api_url)
|
||||
@@ -185,65 +253,78 @@ func (receiver StockDataApi) GetStockBaseInfo() {
|
||||
return
|
||||
}
|
||||
for _, item := range res.Data.Items {
|
||||
ID, _ := convertor.ToInt(item[6])
|
||||
stock := &StockBasic{}
|
||||
stock.Exchange = convertor.ToString(item[0])
|
||||
stock.IsHs = convertor.ToString(item[1])
|
||||
stock.Name = convertor.ToString(item[2])
|
||||
stock.Industry = convertor.ToString(item[3])
|
||||
stock.ListStatus = convertor.ToString(item[4])
|
||||
stock.ActName = convertor.ToString(item[5])
|
||||
stock.ID = uint(ID)
|
||||
stock.CurrType = convertor.ToString(item[7])
|
||||
stock.Area = convertor.ToString(item[8])
|
||||
stock.ListDate = convertor.ToString(item[9])
|
||||
stock.DelistDate = convertor.ToString(item[10])
|
||||
stock.ActEntType = convertor.ToString(item[11])
|
||||
stock.TsCode = convertor.ToString(item[12])
|
||||
stock.Symbol = convertor.ToString(item[13])
|
||||
stock.Cnspell = convertor.ToString(item[14])
|
||||
stock.Fullname = convertor.ToString(item[20])
|
||||
stock.Ename = convertor.ToString(item[21])
|
||||
db.Dao.Model(&StockBasic{}).FirstOrCreate(stock, &StockBasic{TsCode: stock.TsCode}).Updates(stock)
|
||||
data := map[string]any{}
|
||||
for _, field := range strings.Split(fields, ",") {
|
||||
logger.SugaredLogger.Infof("field: %s", field)
|
||||
idx := slice.IndexOf(res.Data.Fields, field)
|
||||
if idx == -1 {
|
||||
continue
|
||||
}
|
||||
data[field] = item[idx]
|
||||
}
|
||||
jsonData, _ := json.Marshal(data)
|
||||
err := json.Unmarshal(jsonData, stock)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
db.Dao.Model(&StockBasic{}).FirstOrCreate(stock, &StockBasic{TsCode: stock.TsCode}).Where("ts_code = ?", stock.TsCode).Updates(stock)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (receiver StockDataApi) GetStockCodeRealTimeData(StockCode string) (*StockInfo, error) {
|
||||
func (receiver StockDataApi) GetStockCodeRealTimeData(StockCodes ...string) (*[]StockInfo, error) {
|
||||
resp, err := receiver.client.R().
|
||||
SetHeader("Host", "hq.sinajs.cn").
|
||||
SetHeader("Referer", "https://finance.sina.com.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").
|
||||
Get(fmt.Sprintf(sina_stook_url, time.Now().Unix(), StockCode))
|
||||
Get(fmt.Sprintf(sina_stook_url, time.Now().Unix(), slice.Join(StockCodes, ",")))
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return &StockInfo{}, nil
|
||||
return &[]StockInfo{}, nil
|
||||
}
|
||||
stockData, err := ParseFullSingleStockData(GB18030ToUTF8(resp.Body()))
|
||||
var count int64
|
||||
db.Dao.Model(&StockInfo{}).Where("code = ?", StockCode).Count(&count)
|
||||
if count == 0 {
|
||||
go db.Dao.Model(&StockInfo{}).Create(stockData)
|
||||
} else {
|
||||
go db.Dao.Model(&StockInfo{}).Where("code = ?", StockCode).Updates(stockData)
|
||||
|
||||
stockInfos := make([]StockInfo, 0)
|
||||
str := GB18030ToUTF8(resp.Body())
|
||||
dataStr := strutil.SplitEx(str, "\n", true)
|
||||
for _, data := range dataStr {
|
||||
//logger.SugaredLogger.Info(data)
|
||||
stockData, err := ParseFullSingleStockData(data)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
stockInfos = append(stockInfos, *stockData)
|
||||
}
|
||||
return stockData, err
|
||||
//var count int64
|
||||
//db.Dao.Model(&StockInfo{}).Where("code = ?", StockCode).Count(&count)
|
||||
//if count == 0 {
|
||||
// go db.Dao.Model(&StockInfo{}).Create(stockData)
|
||||
//} else {
|
||||
// go db.Dao.Model(&StockInfo{}).Where("code = ?", StockCode).Updates(stockData)
|
||||
//}
|
||||
return &stockInfos, err
|
||||
}
|
||||
|
||||
func (receiver StockDataApi) Follow(stockCode string) string {
|
||||
stockInfo, err := receiver.GetStockCodeRealTimeData(stockCode)
|
||||
logger.SugaredLogger.Infof("Follow %s", stockCode)
|
||||
stockInfos, err := receiver.GetStockCodeRealTimeData(stockCode)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return "关注失败"
|
||||
}
|
||||
stockInfo := (*stockInfos)[0]
|
||||
price, _ := convertor.ToFloat(stockInfo.Price)
|
||||
db.Dao.Model(&FollowedStock{}).FirstOrCreate(&FollowedStock{
|
||||
StockCode: stockCode,
|
||||
Name: stockInfo.Name,
|
||||
Price: price,
|
||||
Time: time.Now(),
|
||||
ChangePercent: 0,
|
||||
PriceChange: 0,
|
||||
StockCode: stockCode,
|
||||
Name: stockInfo.Name,
|
||||
Price: price,
|
||||
Time: time.Now(),
|
||||
ChangePercent: 0,
|
||||
PriceChange: 0,
|
||||
Sort: 0,
|
||||
AlarmChangePercent: 3,
|
||||
AlarmPrice: price + 1,
|
||||
}, &FollowedStock{StockCode: stockCode})
|
||||
return "关注成功"
|
||||
}
|
||||
@@ -262,6 +343,22 @@ func (receiver StockDataApi) SetCostPriceAndVolume(price float64, volume int64,
|
||||
return "设置成功"
|
||||
}
|
||||
|
||||
func (receiver StockDataApi) SetAlarmChangePercent(val, alarmPrice float64, stockCode string) string {
|
||||
err := db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", stockCode).Updates(&map[string]any{
|
||||
"alarm_change_percent": val,
|
||||
"alarm_price": alarmPrice,
|
||||
}).Error
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return "设置失败"
|
||||
}
|
||||
return "设置成功"
|
||||
}
|
||||
|
||||
func (receiver StockDataApi) SetStockSort(sort int64, stockCode string) {
|
||||
db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", stockCode).Update("sort", sort)
|
||||
}
|
||||
|
||||
func (receiver StockDataApi) GetFollowList() []FollowedStock {
|
||||
var result []FollowedStock
|
||||
db.Dao.Model(&FollowedStock{}).Order("sort asc,time desc").Find(&result)
|
||||
@@ -271,6 +368,20 @@ func (receiver StockDataApi) GetFollowList() []FollowedStock {
|
||||
func (receiver StockDataApi) GetStockList(key string) []StockBasic {
|
||||
var result []StockBasic
|
||||
db.Dao.Model(&StockBasic{}).Where("name like ? or ts_code like ?", "%"+key+"%", "%"+key+"%").Find(&result)
|
||||
var result2 []IndexBasic
|
||||
db.Dao.Model(&IndexBasic{}).Where("market in ?", []string{"SSE", "SZSE"}).Where("name like ? or ts_code like ?", "%"+key+"%", "%"+key+"%").Find(&result2)
|
||||
|
||||
for _, item := range result2 {
|
||||
result = append(result, StockBasic{
|
||||
TsCode: item.TsCode,
|
||||
Name: item.Name,
|
||||
Fullname: item.FullName,
|
||||
Symbol: item.Symbol,
|
||||
Market: item.Market,
|
||||
ListDate: item.ListDate,
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -369,3 +480,24 @@ func ParseFullSingleStockData(data string) (*StockInfo, error) {
|
||||
|
||||
return stockInfo, nil
|
||||
}
|
||||
|
||||
type IndexBasic struct {
|
||||
gorm.Model
|
||||
TsCode string `json:"ts_code" gorm:"index"`
|
||||
Symbol string `json:"symbol" gorm:"index"`
|
||||
Name string `json:"name" gorm:"index"`
|
||||
FullName string `json:"fullname"`
|
||||
IndexType string `json:"index_type"`
|
||||
IndexCategory string `json:"category"`
|
||||
Market string `json:"market"`
|
||||
ListDate string `json:"list_date"`
|
||||
BaseDate string `json:"base_date"`
|
||||
BasePoint float64 `json:"base_point"`
|
||||
Publisher string `json:"publisher"`
|
||||
WeightRule string `json:"weight_rule"`
|
||||
DESC string `json:"desc"`
|
||||
}
|
||||
|
||||
func (IndexBasic) TableName() string {
|
||||
return "tushare_index_basic"
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@ package data
|
||||
|
||||
import (
|
||||
"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"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
@@ -14,9 +19,28 @@ import (
|
||||
// @Desc
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
func TestParseFullSingleStockData(t *testing.T) {
|
||||
resp, err := resty.New().R().
|
||||
SetHeader("Host", "hq.sinajs.cn").
|
||||
SetHeader("Referer", "https://finance.sina.com.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").
|
||||
Get(fmt.Sprintf(sina_stook_url, time.Now().Unix(), "sh600859,sh600745"))
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
}
|
||||
data := GB18030ToUTF8(resp.Body())
|
||||
strs := strutil.SplitEx(data, "\n", true)
|
||||
for _, str := range strs {
|
||||
logger.SugaredLogger.Info(str)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStockDataApi(t *testing.T) {
|
||||
stockDataApi := NewStockDataApi()
|
||||
t.Log(stockDataApi.GetStockCodeRealTimeData("sh600859"))
|
||||
datas, _ := stockDataApi.GetStockCodeRealTimeData("sh600859", "sh600745")
|
||||
for _, data := range *datas {
|
||||
t.Log(data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStockBaseInfo(t *testing.T) {
|
||||
@@ -74,6 +98,12 @@ func TestReadFile(t *testing.T) {
|
||||
func TestFollowedList(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
stockDataApi := NewStockDataApi()
|
||||
t.Log(stockDataApi.GetFollowList())
|
||||
stockDataApi.GetFollowList()
|
||||
|
||||
}
|
||||
|
||||
func TestStockDataApi_GetIndexBasic(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
stockDataApi := NewStockDataApi()
|
||||
stockDataApi.GetIndexBasic()
|
||||
}
|
||||
|
||||
BIN
build/app.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
build/screenshot/img.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
build/screenshot/img_1.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
build/screenshot/img_2.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
build/screenshot/img_3.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
build/screenshot/img_4.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
build/screenshot/img_5.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
14
frontend/package-lock.json
generated
@@ -8,11 +8,12 @@
|
||||
"name": "go-stock",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"vue": "^3.2.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"naive-ui": "^2.40.3",
|
||||
"naive-ui": "^2.41.0",
|
||||
"vfonts": "^0.0.3",
|
||||
"vite": "5.4.6"
|
||||
}
|
||||
@@ -736,6 +737,11 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@vicons/ionicons5": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vicons/ionicons5/-/ionicons5-0.13.0.tgz",
|
||||
"integrity": "sha512-zvZKBPjEXKN7AXNo2Na2uy+nvuv6SP4KAMQxpKL2vfHMj0fSvuw7JZcOPCjQC3e7ayssKnaoFVAhbYcW6v41qQ=="
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz",
|
||||
@@ -990,9 +996,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/naive-ui": {
|
||||
"version": "2.40.3",
|
||||
"resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.40.3.tgz",
|
||||
"integrity": "sha512-TpgYfOg0SNlG4HHhTdFnFcPc1trZiX3r10Pn6biyEgRoi6ZC5qbsY8xgKsqQuG4nWj2PHLT8pPVEkt2pKOlxag==",
|
||||
"version": "2.41.0",
|
||||
"resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.41.0.tgz",
|
||||
"integrity": "sha512-KnmLg+xPLwXV8QVR7ZZ69eCjvel7R5vru8+eFe4VoAJHEgqAJgVph6Zno9K2IVQRpSF3GBGea3tjavslOR4FAA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@css-render/plugin-bem": "^0.15.14",
|
||||
|
||||
@@ -9,11 +9,12 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"vue": "^3.2.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"naive-ui": "^2.40.3",
|
||||
"naive-ui": "^2.41.0",
|
||||
"vfonts": "^0.0.3",
|
||||
"vite": "5.4.6"
|
||||
},
|
||||
|
||||
@@ -1 +1 @@
|
||||
9ce62efac1fed08499bbf20c8a5fd1b2
|
||||
fda5308055dfd8a9cbfbd89ea27276c4
|
||||
@@ -1,11 +1,13 @@
|
||||
<script setup>
|
||||
import stockInfo from './components/stock.vue'
|
||||
import {ref} from "vue";
|
||||
import { darkTheme } from 'naive-ui'
|
||||
|
||||
const content = ref('数据来源于网络,仅供参考\n投资有风险,入市需谨慎')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-config-provider :theme="darkTheme">
|
||||
<n-watermark
|
||||
:content="content"
|
||||
cross
|
||||
@@ -27,6 +29,7 @@ const content = ref('数据来源于网络,仅供参考\n投资有风险,入市
|
||||
</n-message-provider>
|
||||
</n-flex>
|
||||
</n-watermark>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
<style>
|
||||
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
<script setup>
|
||||
import {h, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue'
|
||||
import {Greet, Follow, UnFollow, GetFollowList, GetStockList, SetCostPriceAndVolume} from '../../wailsjs/go/main/App'
|
||||
import {NButton, NFlex, NForm, NFormItem, NInput, NInputNumber, NText, useMessage, useModal} from 'naive-ui'
|
||||
import {computed, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue'
|
||||
import {
|
||||
Follow,
|
||||
GetFollowList,
|
||||
GetStockList,
|
||||
Greet,
|
||||
SendDingDingMessage, SendDingDingMessageByType,
|
||||
SetAlarmChangePercent,
|
||||
SetCostPriceAndVolume, SetStockSort,
|
||||
UnFollow
|
||||
} from '../../wailsjs/go/main/App'
|
||||
import {NButton, NFlex, NForm, NFormItem, NInputNumber, NText, useMessage, useModal} from 'naive-ui'
|
||||
import {EventsOn, WindowFullscreen, WindowReload, WindowUnfullscreen} from '../../wailsjs/runtime'
|
||||
import {Add, Search} from '@vicons/ionicons5'
|
||||
|
||||
const message = useMessage()
|
||||
const modal = useModal()
|
||||
@@ -13,26 +24,44 @@ const stockList=ref([])
|
||||
const followList=ref([])
|
||||
const options=ref([])
|
||||
const modalShow = ref(false)
|
||||
const modalShow2 = ref(false)
|
||||
const modalShow3 = ref(false)
|
||||
const addBTN = ref(true)
|
||||
const formModel = ref({
|
||||
name: "",
|
||||
code: "",
|
||||
costPrice: 0.000,
|
||||
volume: 0
|
||||
volume: 0,
|
||||
alarm: 0,
|
||||
alarmPrice:0,
|
||||
sort:999,
|
||||
})
|
||||
|
||||
const data = reactive({
|
||||
name: "",
|
||||
code: "",
|
||||
fenshiURL:"",
|
||||
kURL:"",
|
||||
resultText: "Please enter your name below 👇",
|
||||
fullscreen: false,
|
||||
})
|
||||
|
||||
const sortedResults = computed(() => {
|
||||
//console.log("computed",sortedResults.value)
|
||||
const sortedKeys =Object.keys(results.value).sort();
|
||||
const sortedObject = {};
|
||||
sortedKeys.forEach(key => {
|
||||
sortedObject[key] = results.value[key];
|
||||
});
|
||||
return sortedObject
|
||||
});
|
||||
|
||||
onBeforeMount(()=>{
|
||||
GetStockList("").then(result => {
|
||||
stockList.value = result
|
||||
options.value=result.map(item => {
|
||||
return {
|
||||
label: item.name+" "+item.ts_code,
|
||||
label: item.name+" - "+item.ts_code,
|
||||
value: item.ts_code
|
||||
}
|
||||
})
|
||||
@@ -51,21 +80,49 @@ onBeforeMount(()=>{
|
||||
|
||||
onMounted(() => {
|
||||
message.loading("Loading...")
|
||||
console.log(`the component is now mounted.`)
|
||||
// console.log(`the component is now mounted.`)
|
||||
|
||||
ticker.value=setInterval(() => {
|
||||
if(isTradingTime()){
|
||||
monitor()
|
||||
//monitor()
|
||||
data.fenshiURL='http://image.sinajs.cn/newchart/min/n/'+data.code+'.gif'+"?t="+Date.now()
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
}, 3500)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
console.log(`the component is now unmounted.`)
|
||||
// console.log(`the component is now unmounted.`)
|
||||
clearInterval(ticker.value)
|
||||
})
|
||||
|
||||
EventsOn("refresh",(data)=>{
|
||||
message.success(data)
|
||||
})
|
||||
|
||||
EventsOn("showSearch",(data)=>{
|
||||
addBTN.value = data === 1;
|
||||
})
|
||||
|
||||
EventsOn("stock_price",(data)=>{
|
||||
//console.log("stock_price",data['股票代码'])
|
||||
updateData(data)
|
||||
})
|
||||
|
||||
EventsOn("refreshFollowList",(data)=>{
|
||||
|
||||
WindowReload()
|
||||
// message.loading("refresh...")
|
||||
// GetFollowList().then(result => {
|
||||
// followList.value = result
|
||||
// for (const followedStock of result) {
|
||||
// if (!stocks.value.includes(followedStock.StockCode)) {
|
||||
// stocks.value.push(followedStock.StockCode)
|
||||
// }
|
||||
// }
|
||||
// monitor()
|
||||
// message.destroyAll
|
||||
// })
|
||||
})
|
||||
|
||||
//判断是否是A股交易时间
|
||||
function isTradingTime() {
|
||||
@@ -93,73 +150,111 @@ function AddStock(){
|
||||
Follow(data.code).then(result => {
|
||||
message.success(result)
|
||||
})
|
||||
monitor()
|
||||
}else{
|
||||
message.error("已经关注了")
|
||||
}
|
||||
monitor()
|
||||
}
|
||||
|
||||
|
||||
|
||||
function removeMonitor(code,name) {
|
||||
console.log("removeMonitor",name,code)
|
||||
function removeMonitor(code,name,key) {
|
||||
console.log("removeMonitor",name,code,key)
|
||||
stocks.value.splice(stocks.value.indexOf(code),1)
|
||||
delete results.value[name]
|
||||
console.log("removeMonitor-key",key)
|
||||
console.log("removeMonitor-v",results.value[key])
|
||||
|
||||
delete results.value[key]
|
||||
console.log("removeMonitor-v",results.value[key])
|
||||
|
||||
UnFollow(code).then(result => {
|
||||
message.success(result)
|
||||
})
|
||||
}
|
||||
|
||||
function getStockList(){
|
||||
function getStockList(value){
|
||||
// console.log("getStockList",value)
|
||||
let result;
|
||||
result=stockList.value.filter(item => item.name.includes(data.name)||item.ts_code.includes(data.name))
|
||||
result=stockList.value.filter(item => item.name.includes(value)||item.ts_code.includes(value))
|
||||
options.value=result.map(item => {
|
||||
return {
|
||||
label: item.name+" "+item.ts_code,
|
||||
label: item.name+" - "+item.ts_code,
|
||||
value: item.ts_code
|
||||
}
|
||||
})
|
||||
if(value&&value.indexOf("-")<=0){
|
||||
data.code=value
|
||||
}
|
||||
}
|
||||
|
||||
function monitor() {
|
||||
async function updateData(result) {
|
||||
if(result["当前价格"]<=0){
|
||||
result["当前价格"]=result["卖一报价"]
|
||||
}
|
||||
|
||||
if (result.changePercent>0) {
|
||||
result.type="error"
|
||||
result.color="#E88080"
|
||||
}else if (result.changePercent<0) {
|
||||
result.type="success"
|
||||
result.color="#63E2B7"
|
||||
}else {
|
||||
result.type="default"
|
||||
result.color="#FFFFFF"
|
||||
}
|
||||
|
||||
if(result.profitAmount>0){
|
||||
result.profitType="error"
|
||||
}else if(result.profitAmount<0){
|
||||
result.profitType="success"
|
||||
}
|
||||
if(result["当前价格"]){
|
||||
if(result.alarmChangePercent>0&&Math.abs(result.changePercent)>=result.alarmChangePercent){
|
||||
SendMessage(result,1)
|
||||
}
|
||||
|
||||
if(result.alarmPrice>0&&result["当前价格"]>=result.alarmPrice){
|
||||
SendMessage(result,2)
|
||||
}
|
||||
|
||||
if(result.costPrice>0&&result["当前价格"]>=result.costPrice){
|
||||
SendMessage(result,3)
|
||||
}
|
||||
}
|
||||
|
||||
result.key=GetSortKey(result.sort,result["股票代码"])
|
||||
results.value[GetSortKey(result.sort,result["股票代码"])]=result
|
||||
}
|
||||
|
||||
|
||||
async function monitor() {
|
||||
for (let code of stocks.value) {
|
||||
// console.log(code)
|
||||
Greet(code).then(result => {
|
||||
let s=(result["当前价格"]-result["昨日收盘价"])*100/result["昨日收盘价"]
|
||||
let roundedNum = s.toFixed(2); // 将数字转换为保留两位小数的字符串形式
|
||||
result.s=roundedNum+"%"
|
||||
|
||||
result.highRate=((result["今日最高价"]-result["今日开盘价"])*100/result["今日开盘价"]).toFixed(2)+"%"
|
||||
result.lowRate=((result["今日最低价"]-result["今日开盘价"])*100/result["今日开盘价"]).toFixed(2)+"%"
|
||||
|
||||
|
||||
if (roundedNum>0) {
|
||||
result.type="error"
|
||||
result.color="#E88080"
|
||||
}else if (roundedNum<0) {
|
||||
result.type="success"
|
||||
result.color="#63E2B7"
|
||||
}else {
|
||||
result.type="default"
|
||||
result.color="#FFFFFF"
|
||||
}
|
||||
let res= followList.value.filter(item => item.StockCode===code)
|
||||
if (res.length>0) {
|
||||
result.costPrice=res[0].CostPrice
|
||||
result.volume=res[0].Volume
|
||||
result.profit=((result["当前价格"]-result.costPrice)*100/result.costPrice).toFixed(3)
|
||||
result.profitAmountToday=(result.volume*(result["当前价格"]-result["昨日收盘价"])).toFixed(2)
|
||||
result.profitAmount=(result.volume*(result["当前价格"]-result.costPrice)).toFixed(2)
|
||||
if(result.profitAmount>0){
|
||||
result.profitType="error"
|
||||
}else if(result.profitAmount<0){
|
||||
result.profitType="success"
|
||||
}
|
||||
}
|
||||
results.value[result["股票名称"]]=result
|
||||
updateData(result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//数字长度不够前面补0
|
||||
function padZero(num, length) {
|
||||
return (Array(length).join('0') + num).slice(-length);
|
||||
}
|
||||
|
||||
function GetSortKey(sort,code){
|
||||
return padZero(sort,6)+"_"+code
|
||||
}
|
||||
|
||||
function onSelect(item) {
|
||||
data.code=item.split(".")[1].toLowerCase()+item.split(".")[0]
|
||||
//console.log("onSelect",item)
|
||||
|
||||
if(item.indexOf("-")>0){
|
||||
item=item.split("-")[1].toLowerCase()
|
||||
}
|
||||
if(item.indexOf(".")>0){
|
||||
data.code=item.split(".")[1].toLowerCase()+item.split(".")[0]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function search(code,name){
|
||||
@@ -169,16 +264,44 @@ function search(code,name){
|
||||
}
|
||||
function setStock(code,name){
|
||||
let res=followList.value.filter(item => item.StockCode===code)
|
||||
console.log("res:",res)
|
||||
//console.log("res:",res)
|
||||
formModel.value.name=name
|
||||
formModel.value.code=code
|
||||
formModel.value.volume=res[0].Volume
|
||||
formModel.value.costPrice=res[0].CostPrice
|
||||
formModel.value.alarm=res[0].AlarmChangePercent
|
||||
formModel.value.alarmPrice=res[0].AlarmPrice
|
||||
formModel.value.sort=res[0].Sort
|
||||
modalShow.value=true
|
||||
}
|
||||
|
||||
function updateCostPriceAndVolumeNew(code,price,volume){
|
||||
console.log(code,price,volume)
|
||||
function showFenshi(code,name){
|
||||
data.code=code
|
||||
data.name=name
|
||||
data.fenshiURL='http://image.sinajs.cn/newchart/min/n/'+data.code+'.gif'+"?t="+Date.now()
|
||||
modalShow2.value=true
|
||||
}
|
||||
function showK(code,name){
|
||||
data.code=code
|
||||
data.name=name
|
||||
data.kURL='http://image.sinajs.cn/newchart/daily/n/'+data.code+'.gif'+"?t="+Date.now()
|
||||
modalShow3.value=true
|
||||
}
|
||||
|
||||
|
||||
function updateCostPriceAndVolumeNew(code,price,volume,alarm,formModel){
|
||||
|
||||
if(formModel.sort){
|
||||
SetStockSort(formModel.sort,code).then(result => {
|
||||
//message.success(result)
|
||||
})
|
||||
}
|
||||
|
||||
if(alarm||formModel.alarmPrice){
|
||||
SetAlarmChangePercent(alarm,formModel.alarmPrice,code).then(result => {
|
||||
//message.success(result)
|
||||
})
|
||||
}
|
||||
SetCostPriceAndVolume(code,price,volume).then(result => {
|
||||
modalShow.value=false
|
||||
message.success(result)
|
||||
@@ -194,69 +317,192 @@ function updateCostPriceAndVolumeNew(code,price,volume){
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function fullscreen(){
|
||||
if(data.fullscreen){
|
||||
WindowUnfullscreen()
|
||||
}else{
|
||||
WindowFullscreen()
|
||||
}
|
||||
data.fullscreen=!data.fullscreen
|
||||
}
|
||||
|
||||
|
||||
//type 报警类型: 1 涨跌报警;2 股价报警 3 成本价报警
|
||||
function SendMessage(result,type){
|
||||
let typeName=getTypeName(type)
|
||||
let img='http://image.sinajs.cn/newchart/min/n/'+result["股票代码"]+'.gif'+"?t="+Date.now()
|
||||
let markdown="### go-stock ["+typeName+"]\n\n"+
|
||||
"### "+result["股票名称"]+"("+result["股票代码"]+")\n" +
|
||||
"- 当前价格: "+result["当前价格"]+" "+result.s+"\n" +
|
||||
"- 最高价: "+result["今日最高价"]+" "+result.highRate+"\n" +
|
||||
"- 最低价: "+result["今日最低价"]+" "+result.lowRate+"\n" +
|
||||
"- 昨收价: "+result["昨日收盘价"]+"\n" +
|
||||
"- 今开价: "+result["今日开盘价"]+"\n" +
|
||||
"- 成本价: "+result.costPrice+" "+result.profit+"% "+result.profitAmount+" ¥\n" +
|
||||
"- 成本数量: "+result.volume+"股\n" +
|
||||
"- 日期: "+result["日期"]+" "+result["时间"]+"\n\n"+
|
||||
"\n"
|
||||
let title=result["股票名称"]+"("+result["股票代码"]+") "+result["当前价格"]+" "+result.s
|
||||
|
||||
let msg='{' +
|
||||
' "msgtype": "markdown",' +
|
||||
' "markdown": {' +
|
||||
' "title":"['+typeName+"]"+title+'",' +
|
||||
' "text": "'+markdown+'"' +
|
||||
' },' +
|
||||
' "at": {' +
|
||||
' "isAtAll": true' +
|
||||
' }' +
|
||||
' }'
|
||||
// SendDingDingMessage(msg,result["股票代码"])
|
||||
SendDingDingMessageByType(msg,result["股票代码"],type)
|
||||
}
|
||||
|
||||
function getTypeName(type){
|
||||
switch (type)
|
||||
{
|
||||
case 1:
|
||||
return "涨跌报警"
|
||||
case 2:
|
||||
return "股价报警"
|
||||
case 3:
|
||||
return "成本价报警"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
//获取高度
|
||||
function getHeight() {
|
||||
return document.documentElement.clientHeight
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-grid :x-gap="8" :cols="3" :y-gap="8">
|
||||
<n-gi v-for="result in results" >
|
||||
<n-card size="medium" style="min-height: 270px" :data-code="result['股票代码']" :bordered="false" :title="result['股票名称']" :content-style="'font-size: 18px;'" closable @close="removeMonitor(result['股票代码'],result['股票名称'])">
|
||||
<n-grid :x-gap="8" :cols="3" :y-gap="8" >
|
||||
<n-gi v-for="result in sortedResults" >
|
||||
<n-card :data-code="result['股票代码']" :bordered="false" :title="result['股票名称']" :closable="true" @close="removeMonitor(result['股票代码'],result['股票名称'],result.key)">
|
||||
<n-grid :cols="1" :y-gap="6">
|
||||
<n-gi>
|
||||
<n-text :type="result.type" >{{result["当前价格"]}}</n-text><n-text style="padding-left: 10px;" :type="result.type">{{ result.s}}</n-text>
|
||||
<n-text size="small" v-if="result.profitAmountToday>0" :type="result.type">{{result.profitAmountToday}}</n-text>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-grid :cols="2" :y-gap="4" :x-gap="4" :item-style="'font-size: 14px;'">
|
||||
<n-gi>
|
||||
<n-text :type="'info'">{{"最高 "+result["今日最高价"]+" "+result.highRate }}</n-text>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-text :type="'info'">{{"最低 "+result["今日最低价"]+" "+result.lowRate }}</n-text>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-text :type="'info'">{{"昨收 "+result["昨日收盘价"]}}</n-text>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-text :type="'info'">{{"今开 "+result["今日开盘价"]}}</n-text>
|
||||
<n-text :type="result.type" >
|
||||
<n-number-animation :duration="1000" :precision="2" :from="result['上次当前价格']" :to="Number(result['当前价格'])" />
|
||||
</n-text>
|
||||
<n-text style="padding-left: 10px;" :type="result.type">
|
||||
<n-number-animation :duration="1000" :precision="3" :from="0" :to="result.changePercent" />%
|
||||
</n-text>
|
||||
<n-text size="small" v-if="result.costVolume>0" :type="result.type">
|
||||
<n-number-animation :duration="1000" :precision="2" :from="0" :to="result.profitAmountToday" />
|
||||
</n-text>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-grid :cols="2" :y-gap="4" :x-gap="4" >
|
||||
<n-gi>
|
||||
<n-text :type="'info'">{{"最高 "+result["今日最高价"]+" "+result.highRate }}%</n-text>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-text :type="'info'">{{"最低 "+result["今日最低价"]+" "+result.lowRate }}%</n-text>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-text :type="'info'">{{"昨收 "+result["昨日收盘价"]}}</n-text>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-text :type="'info'">{{"今开 "+result["今日开盘价"]}}</n-text>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<template #header-extra>
|
||||
<n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{result.volume+"股"}}</n-tag>
|
||||
<!-- <n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{result.volume+"股"}}</n-tag>-->
|
||||
</template>
|
||||
<template #footer>
|
||||
<n-tag size="small" v-if="result.costPrice>0" :type="result.profitType">{{"成本:"+result.costPrice+" "+result.profit+"%"+" ( "+result.profitAmount+" ¥ )"}}</n-tag>
|
||||
<n-flex justify="center">
|
||||
<n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{result.volume+"股"}}</n-tag>
|
||||
<n-tag size="small" v-if="result.costPrice>0" :type="result.profitType">{{"成本:"+result.costPrice+"*"+result.costVolume+" "+result.profit+"%"+" ( "+result.profitAmount+" ¥ )"}}</n-tag>
|
||||
</n-flex>
|
||||
</template>
|
||||
<template #action>
|
||||
<n-flex justify="space-between">
|
||||
<n-button size="tiny" type="info" @click="setStock(result['股票代码'],result['股票名称'])"> 设置 </n-button>
|
||||
<n-button size="tiny" type="warning" @click="search(result['股票代码'],result['股票名称'])"> 详情 </n-button>
|
||||
<n-text :type="'info'">{{result["日期"]+" "+result["时间"]}}</n-text>
|
||||
<n-button size="tiny" type="info" @click="setStock(result['股票代码'],result['股票名称'])"> 成本 </n-button>
|
||||
<n-button size="tiny" type="success" @click="showFenshi(result['股票代码'],result['股票名称'])"> 分时 </n-button>
|
||||
<n-button size="tiny" type="error" @click="showK(result['股票代码'],result['股票名称'])"> 日K </n-button>
|
||||
<n-button size="tiny" type="warning" @click="search(result['股票代码'],result['股票名称'])"> 详情 </n-button>
|
||||
<!-- <n-button size="tiny" type="info" @click="SendMessage(result)"> 钉钉 </n-button>-->
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-card >
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-auto-complete v-model:value="data.name" type="text"
|
||||
:input-props="{
|
||||
autocomplete: 'disabled',
|
||||
}"
|
||||
:options="options"
|
||||
placeholder="股票名称或者代码"
|
||||
clearable class="input" @input="getStockList" :on-select="onSelect"/>
|
||||
<n-button type="info" @click="AddStock"> 添加 </n-button>
|
||||
<n-affix :trigger-bottom="getHeight()/2-10" style="left:0">
|
||||
<!-- <n-card :bordered="false">-->
|
||||
<n-input-group>
|
||||
|
||||
<n-button type="error" @click="addBTN=!addBTN" > <n-icon :component="Search"/> <n-text v-if="addBTN">隐藏</n-text></n-button>
|
||||
<n-auto-complete v-model:value="data.name" v-if="addBTN"
|
||||
:input-props="{
|
||||
autocomplete: 'disabled',
|
||||
}"
|
||||
:options="options"
|
||||
placeholder="请输入股票/指数名称或者代码"
|
||||
clearable @update-value="getStockList" :on-select="onSelect"/>
|
||||
<n-button type="primary" @click="AddStock" v-if="addBTN">
|
||||
<n-icon :component="Add"/> 关注该股票
|
||||
</n-button>
|
||||
</n-input-group>
|
||||
<!-- </n-card>-->
|
||||
|
||||
</n-affix>
|
||||
<n-modal transform-origin="center" size="small" v-model:show="modalShow" :title="formModel.name" style="width: 400px" :preset="'card'">
|
||||
<n-form :model="formModel" :rules="{ costPrice: { required: true, message: '请输入成本'}, volume: { required: true, message: '请输入数量'} }" label-placement="left" label-width="80px">
|
||||
<n-form-item label="成本(元)" path="costPrice">
|
||||
<n-input-number v-model:value="formModel.costPrice" min="0" placeholder="请输入股票成本" />
|
||||
<n-form :model="formModel" :rules="{
|
||||
costPrice: { required: true, message: '请输入成本'},
|
||||
volume: { required: true, message: '请输入数量'},
|
||||
alarm:{required: true, message: '涨跌报警值'} ,
|
||||
alarmPrice: { required: true, message: '请输入报警价格'},
|
||||
sort: { required: true, message: '请输入排序值'},
|
||||
}" label-placement="left" label-width="80px">
|
||||
<n-form-item label="股票成本" path="costPrice">
|
||||
<n-input-number v-model:value="formModel.costPrice" min="0" placeholder="请输入股票成本" >
|
||||
<template #suffix>
|
||||
¥
|
||||
</template>
|
||||
</n-input-number>
|
||||
</n-form-item>
|
||||
<n-form-item label="数量(股)" path="volume">
|
||||
<n-input-number v-model:value="formModel.volume" min="0" placeholder="请输入股票数量" />
|
||||
<n-form-item label="股票数量" path="volume">
|
||||
<n-input-number v-model:value="formModel.volume" min="0" step="100" placeholder="请输入股票数量" >
|
||||
<template #suffix>
|
||||
股
|
||||
</template>
|
||||
</n-input-number>
|
||||
</n-form-item>
|
||||
<n-form-item label="涨跌提醒" path="alarm">
|
||||
<n-input-number v-model:value="formModel.alarm" min="0" placeholder="请输入涨跌报警值(%)" >
|
||||
<template #suffix>
|
||||
%
|
||||
</template>
|
||||
</n-input-number>
|
||||
</n-form-item>
|
||||
<n-form-item label="股价提醒" path="alarmPrice">
|
||||
<n-input-number v-model:value="formModel.alarmPrice" min="0" placeholder="请输入股价报警值(¥)" >
|
||||
<template #suffix>
|
||||
¥
|
||||
</template>
|
||||
</n-input-number>
|
||||
</n-form-item>
|
||||
<n-form-item label="股票排序" path="sort">
|
||||
<n-input-number v-model:value="formModel.sort" min="0" placeholder="请输入股价排序值" >
|
||||
</n-input-number>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #footer>
|
||||
<n-button type="primary" @click="updateCostPriceAndVolumeNew(formModel.code,formModel.costPrice,formModel.volume)">保存</n-button>
|
||||
<n-button type="primary" @click="updateCostPriceAndVolumeNew(formModel.code,formModel.costPrice,formModel.volume,formModel.alarm,formModel)">保存</n-button>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
<n-modal v-model:show="modalShow2" :title="data.name" style="width: 600px" :preset="'card'">
|
||||
<n-image :src="data.fenshiURL" />
|
||||
</n-modal>
|
||||
<n-modal v-model:show="modalShow3" :title="data.name" style="width: 600px" :preset="'card'">
|
||||
<n-image :src="data.kURL" />
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
8
frontend/wailsjs/go/main/App.d.ts
vendored
@@ -10,6 +10,14 @@ export function GetStockList(arg1:string):Promise<Array<data.StockBasic>>;
|
||||
|
||||
export function Greet(arg1:string):Promise<data.StockInfo>;
|
||||
|
||||
export function SendDingDingMessage(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function SendDingDingMessageByType(arg1:string,arg2:string,arg3:number):Promise<string>;
|
||||
|
||||
export function SetAlarmChangePercent(arg1:number,arg2:number,arg3:string):Promise<string>;
|
||||
|
||||
export function SetCostPriceAndVolume(arg1:string,arg2:number,arg3:number):Promise<string>;
|
||||
|
||||
export function SetStockSort(arg1:number,arg2:string):Promise<void>;
|
||||
|
||||
export function UnFollow(arg1:string):Promise<string>;
|
||||
|
||||
@@ -18,10 +18,26 @@ export function Greet(arg1) {
|
||||
return window['go']['main']['App']['Greet'](arg1);
|
||||
}
|
||||
|
||||
export function SendDingDingMessage(arg1, arg2) {
|
||||
return window['go']['main']['App']['SendDingDingMessage'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function SendDingDingMessageByType(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['SendDingDingMessageByType'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function SetAlarmChangePercent(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['SetAlarmChangePercent'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function SetCostPriceAndVolume(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['SetCostPriceAndVolume'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function SetStockSort(arg1, arg2) {
|
||||
return window['go']['main']['App']['SetStockSort'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function UnFollow(arg1) {
|
||||
return window['go']['main']['App']['UnFollow'](arg1);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,12 @@ export namespace data {
|
||||
Price: number;
|
||||
PriceChange: number;
|
||||
ChangePercent: number;
|
||||
AlarmChangePercent: number;
|
||||
AlarmPrice: number;
|
||||
// Go type: time
|
||||
Time: any;
|
||||
Sort: number;
|
||||
IsDel: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new FollowedStock(source);
|
||||
@@ -25,8 +28,11 @@ export namespace data {
|
||||
this.Price = source["Price"];
|
||||
this.PriceChange = source["PriceChange"];
|
||||
this.ChangePercent = source["ChangePercent"];
|
||||
this.AlarmChangePercent = source["AlarmChangePercent"];
|
||||
this.AlarmPrice = source["AlarmPrice"];
|
||||
this.Time = this.convertValues(source["Time"], null);
|
||||
this.Sort = source["Sort"];
|
||||
this.IsDel = source["IsDel"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
@@ -132,13 +138,14 @@ export namespace data {
|
||||
"时间": string;
|
||||
"股票代码": string;
|
||||
"股票名称": string;
|
||||
"上次当前价格": number;
|
||||
"当前价格": string;
|
||||
"成交的股票数": string;
|
||||
"成交金额": string;
|
||||
"今日开盘价": string;
|
||||
"昨日收盘价": string;
|
||||
"今日最低价": string;
|
||||
"今日最高价": string;
|
||||
"今日最低价": string;
|
||||
"竞买价": string;
|
||||
"竞卖价": string;
|
||||
"买一报价": string;
|
||||
@@ -161,6 +168,18 @@ export namespace data {
|
||||
"卖四申报": string;
|
||||
"卖五报价": string;
|
||||
"卖五申报": string;
|
||||
changePercent: number;
|
||||
changePrice: number;
|
||||
highRate: number;
|
||||
lowRate: number;
|
||||
costPrice: number;
|
||||
costVolume: number;
|
||||
profit: number;
|
||||
profitAmount: number;
|
||||
profitAmountToday: number;
|
||||
sort: number;
|
||||
alarmChangePercent: number;
|
||||
alarmPrice: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new StockInfo(source);
|
||||
@@ -176,13 +195,14 @@ export namespace data {
|
||||
this["时间"] = source["时间"];
|
||||
this["股票代码"] = source["股票代码"];
|
||||
this["股票名称"] = source["股票名称"];
|
||||
this["上次当前价格"] = source["上次当前价格"];
|
||||
this["当前价格"] = source["当前价格"];
|
||||
this["成交的股票数"] = source["成交的股票数"];
|
||||
this["成交金额"] = source["成交金额"];
|
||||
this["今日开盘价"] = source["今日开盘价"];
|
||||
this["昨日收盘价"] = source["昨日收盘价"];
|
||||
this["今日最低价"] = source["今日最低价"];
|
||||
this["今日最高价"] = source["今日最高价"];
|
||||
this["今日最低价"] = source["今日最低价"];
|
||||
this["竞买价"] = source["竞买价"];
|
||||
this["竞卖价"] = source["竞卖价"];
|
||||
this["买一报价"] = source["买一报价"];
|
||||
@@ -205,6 +225,18 @@ export namespace data {
|
||||
this["卖四申报"] = source["卖四申报"];
|
||||
this["卖五报价"] = source["卖五报价"];
|
||||
this["卖五申报"] = source["卖五申报"];
|
||||
this.changePercent = source["changePercent"];
|
||||
this.changePrice = source["changePrice"];
|
||||
this.highRate = source["highRate"];
|
||||
this.lowRate = source["lowRate"];
|
||||
this.costPrice = source["costPrice"];
|
||||
this.costVolume = source["costVolume"];
|
||||
this.profit = source["profit"];
|
||||
this.profitAmount = source["profitAmount"];
|
||||
this.profitAmountToday = source["profitAmountToday"];
|
||||
this.sort = source["sort"];
|
||||
this.alarmChangePercent = source["alarmChangePercent"];
|
||||
this.alarmPrice = source["alarmPrice"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
|
||||
14
go.mod
@@ -5,22 +5,34 @@ go 1.21
|
||||
toolchain go1.23.0
|
||||
|
||||
require (
|
||||
github.com/coocood/freecache v1.2.4
|
||||
github.com/duke-git/lancet/v2 v2.3.4
|
||||
github.com/getlantern/systray v1.2.2
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-resty/resty/v2 v2.16.2
|
||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
|
||||
github.com/wailsapp/wails/v2 v2.9.2
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/text v0.16.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gorm.io/gorm v1.25.12
|
||||
gorm.io/plugin/dbresolver v1.5.3
|
||||
gorm.io/plugin/soft_delete v1.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
||||
@@ -34,6 +46,8 @@ require (
|
||||
github.com/leaanthony/u v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
|
||||
44
go.sum
@@ -1,5 +1,9 @@
|
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/coocood/freecache v1.2.4 h1:UdR6Yz/X1HW4fZOuH0Z94KwG851GWOSknua5VUbb/5M=
|
||||
github.com/coocood/freecache v1.2.4/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -7,6 +11,20 @@ github.com/duke-git/lancet/v2 v2.3.4 h1:8XGI7P9w+/GqmEBEXYaH/XuNiM0f4/90Ioti0IvY
|
||||
github.com/duke-git/lancet/v2 v2.3.4/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||
github.com/getlantern/systray v1.2.2 h1:dCEHtfmvkJG7HZ8lS/sLklTH4RKUcIsKrAD9sThoEBE=
|
||||
github.com/getlantern/systray v1.2.2/go.mod h1:pXFOI1wwqwYXEhLPm9ZGjS2u/vVELeIgNMY5HvhHhcE=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
@@ -17,6 +35,10 @@ github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CA
|
||||
github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=
|
||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
@@ -27,6 +49,8 @@ github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4P
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
|
||||
@@ -44,6 +68,8 @@ github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/
|
||||
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
|
||||
github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI=
|
||||
github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
@@ -53,6 +79,13 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -67,7 +100,9 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
@@ -112,6 +147,7 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -122,6 +158,7 @@ golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
@@ -144,6 +181,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
@@ -153,11 +191,17 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/driver/sqlite v1.1.3 h1:BYfdVuZB5He/u9dt4qDpZqiqDJ6KhPqs5QUqsr/Eeuc=
|
||||
gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c=
|
||||
gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU=
|
||||
gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE=
|
||||
gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU=
|
||||
gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
|
||||
52
main.go
@@ -6,13 +6,18 @@ import (
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"go-stock/backend/data"
|
||||
"go-stock/backend/db"
|
||||
"log"
|
||||
"os"
|
||||
goruntime "runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed frontend/dist
|
||||
@@ -21,6 +26,9 @@ var assets embed.FS
|
||||
//go:embed build/appicon.png
|
||||
var icon []byte
|
||||
|
||||
//go:embed build/app.ico
|
||||
var icon2 []byte
|
||||
|
||||
//go:embed build/stock_basic.json
|
||||
var stocksBin []byte
|
||||
|
||||
@@ -31,15 +39,44 @@ func main() {
|
||||
db.Dao.AutoMigrate(&data.StockInfo{})
|
||||
db.Dao.AutoMigrate(&data.StockBasic{})
|
||||
db.Dao.AutoMigrate(&data.FollowedStock{})
|
||||
db.Dao.AutoMigrate(&data.IndexBasic{})
|
||||
|
||||
if stocksBin != nil && len(stocksBin) > 0 {
|
||||
initStockData()
|
||||
go initStockData()
|
||||
}
|
||||
|
||||
data.NewStockDataApi().GetStockBaseInfo()
|
||||
updateBasicInfo()
|
||||
|
||||
// Create an instance of the app structure
|
||||
app := NewApp()
|
||||
AppMenu := menu.NewMenu()
|
||||
FileMenu := AppMenu.AddSubmenu("设置")
|
||||
FileMenu.AddText("显示搜索框", keys.CmdOrCtrl("s"), func(callbackData *menu.CallbackData) {
|
||||
runtime.EventsEmit(app.ctx, "showSearch", 1)
|
||||
})
|
||||
FileMenu.AddText("隐藏搜索框", keys.CmdOrCtrl("d"), func(callbackData *menu.CallbackData) {
|
||||
runtime.EventsEmit(app.ctx, "showSearch", 0)
|
||||
})
|
||||
FileMenu.AddText("刷新数据", keys.CmdOrCtrl("r"), func(callbackData *menu.CallbackData) {
|
||||
//runtime.EventsEmit(app.ctx, "refresh", "setting-"+time.Now().Format("2006-01-02 15:04:05"))
|
||||
runtime.EventsEmit(app.ctx, "refreshFollowList", "refresh-"+time.Now().Format("2006-01-02 15:04:05"))
|
||||
})
|
||||
FileMenu.AddSeparator()
|
||||
FileMenu.AddText("窗口全屏", keys.CmdOrCtrl("f"), func(callback *menu.CallbackData) {
|
||||
runtime.WindowFullscreen(app.ctx)
|
||||
})
|
||||
FileMenu.AddText("窗口还原", keys.Key("Esc"), func(callback *menu.CallbackData) {
|
||||
runtime.WindowUnfullscreen(app.ctx)
|
||||
})
|
||||
|
||||
if goruntime.GOOS == "windows" {
|
||||
FileMenu.AddText("隐藏到托盘区", keys.CmdOrCtrl("h"), func(_ *menu.CallbackData) {
|
||||
runtime.WindowHide(app.ctx)
|
||||
})
|
||||
}
|
||||
|
||||
//FileMenu.AddText("退出", keys.CmdOrCtrl("q"), func(_ *menu.CallbackData) {
|
||||
// runtime.Quit(app.ctx)
|
||||
//})
|
||||
|
||||
// Create application with options
|
||||
err := wails.Run(&options.App{
|
||||
@@ -57,7 +94,7 @@ func main() {
|
||||
HideWindowOnClose: false,
|
||||
BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255},
|
||||
Assets: assets,
|
||||
Menu: nil,
|
||||
Menu: AppMenu,
|
||||
Logger: nil,
|
||||
LogLevel: logger.DEBUG,
|
||||
OnStartup: app.startup,
|
||||
@@ -100,6 +137,13 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func updateBasicInfo() {
|
||||
//更新基本信息
|
||||
go data.NewStockDataApi().GetStockBaseInfo()
|
||||
go data.NewStockDataApi().GetIndexBasic()
|
||||
}
|
||||
|
||||
func initStockData() {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"request_id":"359a096c-0a5a-4b07-818f-3241738d5e73","code":40203,"data":null,"msg":"抱歉,您每小时最多访问该接口1次,权限的具体详情访问:https://tushare.pro/document/1?doc_id=108。"}
|
||||