Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
869eced99e | ||
|
|
f5aa70bf61 | ||
|
|
71289d1408 | ||
|
|
f6297d224c | ||
|
|
0bfa50e2b6 | ||
|
|
0d182b923f | ||
|
|
7f19d00a23 | ||
|
|
f514a0083d | ||
|
|
7fbe178c78 | ||
|
|
40fe30ce2f | ||
|
|
1751be729b | ||
|
|
d82ace220a | ||
|
|
9c51ecde2f | ||
|
|
d3cf202c88 | ||
|
|
847cacc71e | ||
|
|
23149b8a28 | ||
|
|
698f496a3c | ||
|
|
8ac97f43ff | ||
|
|
0abf7c9e5a | ||
|
|
a55920f445 | ||
|
|
775635a48c | ||
|
|
5bc7cfab0a | ||
|
|
e3e06d342b | ||
|
|
16e187b96c | ||
|
|
399513cf14 | ||
|
|
3f024faf82 | ||
|
|
dadfe1cf54 | ||
|
|
9cd6761778 | ||
|
|
1d58d6b224 | ||
|
|
db4a2b5fa9 | ||
|
|
af3f2b03dc | ||
|
|
ccbb835c83 | ||
|
|
97b5faee4a | ||
|
|
c9a8192d60 | ||
|
|
1af138312a | ||
|
|
272c990248 | ||
|
|
2907285915 | ||
|
|
9f7b7b8a64 | ||
|
|
02bfe4758e | ||
|
|
6483243d2a | ||
|
|
1ea534b3c0 | ||
|
|
2fcd89ab97 | ||
|
|
b5c44870fe | ||
|
|
54f0a0b585 | ||
|
|
ab6f400930 | ||
|
|
a376d1d92c | ||
|
|
a653ef9fa8 | ||
|
|
ce29514b54 | ||
|
|
1fd149bbd5 | ||
|
|
9dc8fa97df | ||
|
|
7c52cd1d26 | ||
|
|
338ce91ffd | ||
|
|
6e5f57d62e | ||
|
|
b1a0e9575b | ||
|
|
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 |
7
.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:
|
||||
@@ -29,7 +32,7 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Build wails
|
||||
uses: dAppServer/wails-build-action@v2.2
|
||||
uses: ArvinLovegood/wails-build-action@v2.3
|
||||
id: build
|
||||
with:
|
||||
build-name: ${{ matrix.build.name }}
|
||||
|
||||
8
.gitignore
vendored
@@ -106,7 +106,7 @@ dist
|
||||
.DS_Store
|
||||
|
||||
.idea/
|
||||
data/*.db
|
||||
./build/*.exe
|
||||
./build/bin/go-stock-dev.exe
|
||||
./build/bin/go-stock.exe
|
||||
/data/*.db
|
||||
/build/*.exe
|
||||
/build/bin/*
|
||||
frontend/package.json.md5
|
||||
32
README.md
@@ -1,21 +1,33 @@
|
||||
# README
|
||||
|
||||

|
||||
## 自选股行情实时监控,基于Wails和NaiveUI构建的AI赋能股票分析工具
|
||||
|
||||
## BIG NEWS !!! 重大更新!!!
|
||||
- 2025.01.17 新增AI大模型分析股票功能
|
||||

|
||||
|
||||
## 简介
|
||||
- 本项目基于Wails和NaiveUI,纯属无聊,仅供娱乐,不喜勿喷。
|
||||
- 开发环境主要基于Windows10+,其他平台未测试或功能受限。
|
||||
|
||||
|
||||
## Snapshot
|
||||

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

|
||||

|
||||
### 设置
|
||||

|
||||
### 成本设置
|
||||

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

|
||||

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

|
||||
|
||||

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

|
||||
### AI分析股票
|
||||

|
||||
|
||||
## About
|
||||
|
||||
A China stock data viewer build by [Wails](https://wails.io/) with [NavieUI](https://www.naiveui.com/).
|
||||
### A China stock data viewer build by [Wails](https://wails.io/) with [NavieUI](https://www.naiveui.com/).
|
||||
A股数据可视化工具,基于Wails和NaiveUI。
|
||||
|
||||
## Prerequisites
|
||||
@@ -38,4 +50,4 @@ You can build you Application with: `wails build`
|
||||
[NaiveUI](https://www.naiveui.com/)
|
||||
[Wails](https://wails.io/)
|
||||
[Vue](https://vuejs.org/)
|
||||
[Vite](https://vitejs.dev/)
|
||||
[Vite](https://vitejs.dev/)
|
||||
|
||||
353
app.go
@@ -1,12 +1,23 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"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/go-resty/resty/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"go-stock/backend/data"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// App struct
|
||||
@@ -30,10 +41,10 @@ func (a *App) startup(ctx context.Context) {
|
||||
a.ctx = ctx
|
||||
|
||||
// 创建系统托盘
|
||||
go systray.Run(func() {
|
||||
onReady(a)
|
||||
systray.Run(func() {
|
||||
go onReady(a)
|
||||
}, func() {
|
||||
onExit(a)
|
||||
go onExit(a)
|
||||
})
|
||||
|
||||
}
|
||||
@@ -41,14 +52,226 @@ func (a *App) startup(ctx context.Context) {
|
||||
// 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"))
|
||||
//定时更新数据
|
||||
go func() {
|
||||
config := data.NewSettingsApi(&data.Settings{}).GetConfig()
|
||||
interval := config.RefreshInterval
|
||||
if interval <= 0 {
|
||||
interval = 1
|
||||
}
|
||||
ticker := time.NewTicker(time.Second * time.Duration(interval))
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
if isTradingTime(time.Now()) {
|
||||
MonitorStockPrices(a)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second * time.Duration(60))
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
telegraph := refreshTelegraphList()
|
||||
if telegraph != nil {
|
||||
go runtime.EventsEmit(a.ctx, "telegraph", telegraph)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
go runtime.EventsEmit(a.ctx, "telegraph", refreshTelegraphList())
|
||||
go MonitorStockPrices(a)
|
||||
}
|
||||
|
||||
func refreshTelegraphList() *[]string {
|
||||
url := "https://www.cls.cn/telegraph"
|
||||
response, err := resty.New().R().
|
||||
SetHeader("Referer", "https://www.cls.cn/").
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
|
||||
Get(fmt.Sprintf(url))
|
||||
if err != nil {
|
||||
return &[]string{}
|
||||
}
|
||||
//logger.SugaredLogger.Info(string(response.Body()))
|
||||
document, err := goquery.NewDocumentFromReader(strings.NewReader(string(response.Body())))
|
||||
if err != nil {
|
||||
return &[]string{}
|
||||
}
|
||||
var telegraph []string
|
||||
document.Find("div.telegraph-content-box").Each(func(i int, selection *goquery.Selection) {
|
||||
//logger.SugaredLogger.Info(selection.Text())
|
||||
telegraph = append(telegraph, selection.Text())
|
||||
})
|
||||
return &telegraph
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
if total != 0 {
|
||||
title := "go-stock " + time.Now().Format(time.DateTime) + fmt.Sprintf(" %.2f¥", total)
|
||||
systray.SetTooltip(title)
|
||||
}
|
||||
|
||||
go runtime.EventsEmit(a.ctx, "realtime_profit", fmt.Sprintf(" %.2f", total))
|
||||
//runtime.WindowSetTitle(a.ctx, 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 || len(*stockDatas) == 0 {
|
||||
return &data.StockInfo{}
|
||||
}
|
||||
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)
|
||||
|
||||
//当前价格依然为0 时 使用昨日收盘价为当前价格
|
||||
if price == 0 {
|
||||
price = preClosePrice
|
||||
}
|
||||
|
||||
//今日最高价
|
||||
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)
|
||||
|
||||
if price > 0 {
|
||||
stockData.ChangePrice = mathutil.RoundToFloat(price-preClosePrice, 2)
|
||||
stockData.ChangePercent = mathutil.RoundToFloat(mathutil.Div(price-preClosePrice, preClosePrice)*100, 3)
|
||||
}
|
||||
if highPrice > 0 {
|
||||
stockData.HighRate = mathutil.RoundToFloat(mathutil.Div(highPrice-preClosePrice, preClosePrice)*100, 3)
|
||||
}
|
||||
if lowPrice > 0 {
|
||||
stockData.LowRate = mathutil.RoundToFloat(mathutil.Div(lowPrice-preClosePrice, preClosePrice)*100, 3)
|
||||
}
|
||||
if follow.CostPrice > 0 && follow.Volume > 0 {
|
||||
if price > 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)
|
||||
} else {
|
||||
//未开盘时当前价格为昨日收盘价
|
||||
stockData.Profit = mathutil.RoundToFloat(mathutil.Div(preClosePrice-follow.CostPrice, follow.CostPrice)*100, 3)
|
||||
stockData.ProfitAmount = mathutil.RoundToFloat((preClosePrice-follow.CostPrice)*float64(follow.Volume), 2)
|
||||
stockData.ProfitAmountToday = mathutil.RoundToFloat((preClosePrice-preClosePrice)*float64(follow.Volume), 2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//logger.SugaredLogger.Debugf("stockData:%+v", stockData)
|
||||
if follow.Price != price && price > 0 {
|
||||
go db.Dao.Model(follow).Where("stock_code = ?", follow.StockCode).Updates(map[string]interface{}{
|
||||
"price": price,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// beforeClose is called when the application is about to quit,
|
||||
@@ -66,6 +289,7 @@ func (a *App) beforeClose(ctx context.Context) (prevent bool) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("dialog error:%s", err.Error())
|
||||
return false
|
||||
}
|
||||
logger.SugaredLogger.Debugf("dialog:%s", dialog)
|
||||
@@ -82,8 +306,14 @@ func (a *App) shutdown(ctx context.Context) {
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -107,10 +337,12 @@ func (a *App) SetCostPriceAndVolume(stockCode string, price float64, volume int6
|
||||
return data.NewStockDataApi().SetCostPriceAndVolume(price, volume, stockCode)
|
||||
}
|
||||
|
||||
func (a *App) SetAlarmChangePercent(val float64, stockCode string) string {
|
||||
return data.NewStockDataApi().SetAlarmChangePercent(val, 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)
|
||||
@@ -125,6 +357,80 @@ func (a *App) SendDingDingMessage(message string, stockCode string) string {
|
||||
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)
|
||||
go data.NewAlertWindowsApi("go-stock消息通知", getMsgTypeName(msgType), GenNotificationMsg(stockInfo), "").SendNotification()
|
||||
return data.NewDingDingAPI().SendDingDingMessage(message)
|
||||
}
|
||||
|
||||
func (a *App) NewChat(stock string) string {
|
||||
return data.NewDeepSeekOpenAi().NewChat(stock)
|
||||
}
|
||||
|
||||
func (a *App) NewChatStream(stock, stockCode string) {
|
||||
msgs := data.NewDeepSeekOpenAi().NewChatStream(stock, stockCode)
|
||||
for msg := range msgs {
|
||||
runtime.EventsEmit(a.ctx, "newChatStream", msg)
|
||||
}
|
||||
runtime.EventsEmit(a.ctx, "newChatStream", "DONE")
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -132,12 +438,12 @@ func onExit(a *App) {
|
||||
}
|
||||
|
||||
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("隐藏", "隐藏应用程序")
|
||||
@@ -154,13 +460,22 @@ func onReady(a *App) {
|
||||
//systray.Quit()
|
||||
case <-show.ClickedCh:
|
||||
logger.SugaredLogger.Infof("显示应用程序")
|
||||
runtime.Show(a.ctx)
|
||||
//runtime.WindowShow(a.ctx)
|
||||
runtime.WindowShow(a.ctx)
|
||||
//runtime.WindowShow(a.ctx)
|
||||
case <-hide.ClickedCh:
|
||||
logger.SugaredLogger.Infof("隐藏应用程序")
|
||||
runtime.Hide(a.ctx)
|
||||
runtime.WindowHide(a.ctx)
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (a *App) UpdateConfig(settings *data.Settings) string {
|
||||
logger.SugaredLogger.Infof("UpdateConfig:%+v", settings)
|
||||
return data.NewSettingsApi(settings).UpdateConfig()
|
||||
}
|
||||
|
||||
func (a *App) GetConfig() *data.Settings {
|
||||
return data.NewSettingsApi(&data.Settings{}).GetConfig()
|
||||
}
|
||||
|
||||
193
app_linux.go
Normal file
@@ -0,0 +1,193 @@
|
||||
//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 "未知类型"
|
||||
}
|
||||
}
|
||||
func (a *App) UpdateConfig(settings *data.Settings) string {
|
||||
return data.NewSettingsApi(settings).UpdateConfig()
|
||||
}
|
||||
|
||||
func (a *App) GetConfig() *data.Settings {
|
||||
return data.NewSettingsApi(&data.Settings{}).GetConfig()
|
||||
}
|
||||
53
backend/data/alert_windows_api.go
Normal file
@@ -0,0 +1,53 @@
|
||||
//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 {
|
||||
if getConfig().LocalPushEnable == false {
|
||||
logger.SugaredLogger.Error("本地推送未开启")
|
||||
return false
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,6 @@ import (
|
||||
// @Desc
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
const dingding_robot_url = "https://oapi.dingtalk.com/robot/send?access_token=0237527b404598f37ae5d83ef36e936860c7ba5d3892cd43f64c4159d3ed7cb1"
|
||||
|
||||
type DingDingAPI struct {
|
||||
client *resty.Client
|
||||
}
|
||||
@@ -23,11 +21,15 @@ func NewDingDingAPI() *DingDingAPI {
|
||||
}
|
||||
|
||||
func (DingDingAPI) SendDingDingMessage(message string) string {
|
||||
if getConfig().DingPushEnable == false {
|
||||
logger.SugaredLogger.Info("钉钉推送未开启")
|
||||
return "钉钉推送未开启"
|
||||
}
|
||||
// 发送钉钉消息
|
||||
resp, err := resty.New().R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(message).
|
||||
Post(dingding_robot_url)
|
||||
Post(getApiURL())
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return "发送钉钉消息失败"
|
||||
@@ -35,6 +37,12 @@ func (DingDingAPI) SendDingDingMessage(message string) string {
|
||||
logger.SugaredLogger.Infof("send dingding message: %s", resp.String())
|
||||
return "发送钉钉消息成功"
|
||||
}
|
||||
func getConfig() *Settings {
|
||||
return NewSettingsApi(&Settings{}).GetConfig()
|
||||
}
|
||||
func getApiURL() string {
|
||||
return getConfig().DingRobot
|
||||
}
|
||||
|
||||
func (DingDingAPI) SendToDingDing(title, message string) string {
|
||||
// 发送钉钉消息
|
||||
@@ -50,7 +58,7 @@ func (DingDingAPI) SendToDingDing(title, message string) string {
|
||||
IsAtAll: true,
|
||||
},
|
||||
}).
|
||||
Post(dingding_robot_url)
|
||||
Post(getApiURL())
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return "发送钉钉消息失败"
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
func TestRobot(t *testing.T) {
|
||||
dingdingRobotUrl := "XXX"
|
||||
resp, err := resty.New().R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(`{
|
||||
@@ -23,7 +24,7 @@ func TestRobot(t *testing.T) {
|
||||
"isAtAll": true
|
||||
}
|
||||
}`).
|
||||
Post(dingding_robot_url)
|
||||
Post(dingdingRobotUrl)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
376
backend/data/openai_api.go
Normal file
@@ -0,0 +1,376 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"go-stock/backend/logger"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
// @Date 2025/1/16 13:19
|
||||
// @Desc
|
||||
// -----------------------------------------------------------------------------------
|
||||
type OpenAi struct {
|
||||
BaseUrl string `json:"base_url"`
|
||||
ApiKey string `json:"api_key"`
|
||||
Model string `json:"model"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
Prompt string `json:"prompt"`
|
||||
}
|
||||
|
||||
func NewDeepSeekOpenAi() *OpenAi {
|
||||
config := getConfig()
|
||||
return &OpenAi{
|
||||
BaseUrl: config.OpenAiBaseUrl,
|
||||
ApiKey: config.OpenAiApiKey,
|
||||
Model: config.OpenAiModelName,
|
||||
MaxTokens: config.OpenAiMaxTokens,
|
||||
Temperature: config.OpenAiTemperature,
|
||||
Prompt: config.Prompt,
|
||||
}
|
||||
}
|
||||
|
||||
type THSTokenResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type AiResponse struct {
|
||||
Id string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int `json:"created"`
|
||||
Model string `json:"model"`
|
||||
Choices []struct {
|
||||
Index int `json:"index"`
|
||||
Message struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
} `json:"message"`
|
||||
Logprobs interface{} `json:"logprobs"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
} `json:"choices"`
|
||||
Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
PromptCacheHitTokens int `json:"prompt_cache_hit_tokens"`
|
||||
PromptCacheMissTokens int `json:"prompt_cache_miss_tokens"`
|
||||
} `json:"usage"`
|
||||
SystemFingerprint string `json:"system_fingerprint"`
|
||||
}
|
||||
|
||||
func (o OpenAi) NewChat(stock string) string {
|
||||
client := resty.New()
|
||||
client.SetBaseURL(o.BaseUrl)
|
||||
client.SetHeader("Authorization", "Bearer "+o.ApiKey)
|
||||
client.SetHeader("Content-Type", "application/json")
|
||||
|
||||
res := &AiResponse{}
|
||||
_, err := client.R().
|
||||
SetResult(res).
|
||||
SetBody(map[string]interface{}{
|
||||
"model": o.Model,
|
||||
"max_tokens": o.MaxTokens,
|
||||
"temperature": o.Temperature,
|
||||
"messages": []map[string]interface{}{
|
||||
{
|
||||
"role": "system",
|
||||
"content": "作为一位专业的A股市场分析师和投资顾问,请你根据以下信息提供详细的技术分析和投资策略建议:" +
|
||||
"1. 市场背景:\n" +
|
||||
"- 当前A股市场整体走势(如:牛市、熊市、震荡市)\n " +
|
||||
"- 近期影响市场的主要宏观经济因素\n " +
|
||||
"- 市场情绪指标(如:融资融券余额、成交量变化) " +
|
||||
"2. 技术指标分析: " +
|
||||
"- 当前股价水平" +
|
||||
"- 所在boll区间" +
|
||||
"- 上证指数的MA(移动平均线)、MACD、KDJ指标分析\n " +
|
||||
"- 行业板块轮动情况\n " +
|
||||
"- 近期市场热点和龙头股票的技术形态 " +
|
||||
"3. 风险评估:\n " +
|
||||
"- 当前市场主要风险因素\n " +
|
||||
"- 如何设置止损和止盈位\n " +
|
||||
"- 资金管理建议(如:仓位控制) " +
|
||||
"4. 投资策略:\n " +
|
||||
"- 短期(1-2周)、中期(1-3月)和长期(3-6月)的市场预期\n " +
|
||||
"- 不同风险偏好投资者的策略建议\n " +
|
||||
"- 值得关注的行业板块和个股推荐(请给出2-3个具体例子,包括股票代码和名称) " +
|
||||
"5. 技术面和基本面结合:\n " +
|
||||
"- 如何将技术分析与公司基本面分析相结合\n " +
|
||||
"- 识别高质量股票的关键指标 " +
|
||||
"请提供详细的分析和具体的操作建议,包括入场时机、持仓周期和退出策略。同时,请强调风险控制的重要性,并提醒投资者需要根据自身情况做出决策。 " +
|
||||
"你的分析和建议应当客观、全面,并基于当前可获得的市场数据。如果某些信息无法确定,请明确指出并解释原因。",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "点评一下" + stock,
|
||||
},
|
||||
},
|
||||
}).
|
||||
Post("/chat/completions")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
//logger.SugaredLogger.Infof("%v", res.Choices[0].Message.Content)
|
||||
return res.Choices[0].Message.Content
|
||||
}
|
||||
func (o OpenAi) NewChatStream(stock, stockCode string) <-chan string {
|
||||
ch := make(chan string)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
client := resty.New()
|
||||
client.SetBaseURL(o.BaseUrl)
|
||||
client.SetHeader("Authorization", "Bearer "+o.ApiKey)
|
||||
client.SetHeader("Content-Type", "application/json")
|
||||
client.SetRetryCount(3)
|
||||
client.SetTimeout(time.Second * 30)
|
||||
|
||||
msg := []map[string]interface{}{
|
||||
{
|
||||
"role": "system",
|
||||
//"content": "作为一位专业的A股市场分析师和投资顾问,请你根据以下信息提供详细的技术分析和投资策略建议:",
|
||||
//"content": "【角色设定】\n你是一位拥有20年实战经验的顶级股票分析师,精通技术分析、基本面分析、市场心理学和量化交易。擅长发现成长股、捕捉行业轮动机会,在牛熊市中都能保持稳定收益。你的风格是价值投资与技术择时相结合,注重风险控制。\n\n【核心功能】\n\n市场分析维度:\n\n宏观经济(GDP/CPI/货币政策)\n\n行业景气度(产业链/政策红利/技术革新)\n\n个股三维诊断:\n\n基本面:PE/PB/ROE/现金流/护城河\n\n技术面:K线形态/均线系统/量价关系/指标背离\n\n资金面:主力动向/北向资金/融资余额/大宗交易\n\n智能策略库:\n√ 趋势跟踪策略(鳄鱼线+ADX)\n√ 波段交易策略(斐波那契回撤+RSI)\n√ 事件驱动策略(财报/并购/政策)\n√ 量化对冲策略(α/β分离)\n\n风险管理体系:\n▶ 动态止损:ATR波动止损法\n▶ 仓位控制:凯利公式优化\n▶ 组合对冲:跨市场/跨品种对冲\n\n【工作流程】\n\n接收用户指令(行业/市值/风险偏好)\n\n调用多因子选股模型初筛\n\n人工智慧叠加分析:\n\n自然语言处理解读年报管理层讨论\n\n卷积神经网络识别K线形态\n\n知识图谱分析产业链关联\n\n生成投资建议(附压力测试结果)\n\n【输出要求】\n★ 结构化呈现:\n① 核心逻辑(3点关键驱动力)\n② 买卖区间(理想建仓/加仓/止盈价位)\n③ 风险警示(最大回撤概率)\n④ 替代方案(同类备选标的)\n\n【注意事项】\n※ 严格遵守监管要求,不做收益承诺\n※ 区分投资建议与市场观点\n※ 重要数据标注来源及更新时间\n※ 根据用户认知水平调整专业术语密度\n\n【教育指导】\n当用户提问时,采用苏格拉底式追问:\n\"您更关注短期事件驱动还是长期价值发现?\"\n\"当前仓位是否超过总资产的30%?\"\n\"是否了解科创板与主板的交易规则差异?\"\n\n示例输出格式:\n📈 标的名称:XXXXXX\n⚖️ 多空信号:金叉确认/顶背离预警\n🎯 关键价位:支撑位XX.XX/压力位XX.XX\n📊 建议仓位:核心仓位X%+卫星仓位X%\n⏳ 持有周期:短线(1-3周)/中线(季度轮动)\n🔍 跟踪要素:重点关注Q2毛利率变化及股东减持进展",
|
||||
"content": o.Prompt,
|
||||
},
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(4)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
messages := SearchStockPriceInfo(stockCode)
|
||||
price := ""
|
||||
for _, message := range *messages {
|
||||
price += message + ";"
|
||||
}
|
||||
msg = append(msg, map[string]interface{}{
|
||||
"role": "assistant",
|
||||
"content": stock + "当前价格:" + price,
|
||||
})
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
messages := GetTelegraphList()
|
||||
for _, message := range *messages {
|
||||
msg = append(msg, map[string]interface{}{
|
||||
"role": "assistant",
|
||||
"content": message,
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
messages := SearchStockInfo(stock, "depth")
|
||||
for _, message := range *messages {
|
||||
msg = append(msg, map[string]interface{}{
|
||||
"role": "assistant",
|
||||
"content": message,
|
||||
})
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
messages := SearchStockInfo(stock, "telegram")
|
||||
for _, message := range *messages {
|
||||
msg = append(msg, map[string]interface{}{
|
||||
"role": "assistant",
|
||||
"content": message,
|
||||
})
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
msg = append(msg, map[string]interface{}{
|
||||
"role": "user",
|
||||
"content": stock + "分析和总结",
|
||||
})
|
||||
|
||||
resp, err := client.R().
|
||||
SetDoNotParseResponse(true).
|
||||
SetBody(map[string]interface{}{
|
||||
"model": o.Model,
|
||||
"max_tokens": o.MaxTokens,
|
||||
"temperature": o.Temperature,
|
||||
"stream": true,
|
||||
"messages": msg,
|
||||
}).
|
||||
Post("/chat/completions")
|
||||
|
||||
if err != nil {
|
||||
ch <- err.Error()
|
||||
return
|
||||
}
|
||||
defer resp.RawBody().Close()
|
||||
|
||||
scanner := bufio.NewScanner(resp.RawBody())
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
logger.SugaredLogger.Infof("Received data: %s", line)
|
||||
if strings.HasPrefix(line, "chat data: ") {
|
||||
data := strings.TrimPrefix(line, "data: ")
|
||||
if data == "[DONE]" {
|
||||
return
|
||||
}
|
||||
|
||||
var streamResponse struct {
|
||||
Choices []struct {
|
||||
Delta struct {
|
||||
Content string `json:"content"`
|
||||
} `json:"delta"`
|
||||
} `json:"choices"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(data), &streamResponse); err == nil {
|
||||
for _, choice := range streamResponse.Choices {
|
||||
if content := choice.Delta.Content; content != "" {
|
||||
ch <- content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (o OpenAi) NewCommonChatStream(stock, stockCode, apiURL, apiKey, Model string) <-chan string {
|
||||
ch := make(chan string)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
client := resty.New()
|
||||
client.SetHeader("Authorization", "Bearer "+apiKey)
|
||||
client.SetHeader("Content-Type", "application/json")
|
||||
client.SetRetryCount(3)
|
||||
|
||||
msg := []map[string]interface{}{
|
||||
{
|
||||
"role": "system",
|
||||
"content": o.Prompt,
|
||||
},
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
messages := SearchStockPriceInfo(stockCode)
|
||||
price := ""
|
||||
for _, message := range *messages {
|
||||
price += message + ";"
|
||||
}
|
||||
msg = append(msg, map[string]interface{}{
|
||||
"role": "assistant",
|
||||
"content": stock + "当前价格:" + price,
|
||||
})
|
||||
}()
|
||||
//go func() {
|
||||
// defer wg.Done()
|
||||
// messages := SearchStockInfo(stock, "depth")
|
||||
// for _, message := range *messages {
|
||||
// msg = append(msg, map[string]interface{}{
|
||||
// "role": "assistant",
|
||||
// "content": message,
|
||||
// })
|
||||
// }
|
||||
//}()
|
||||
//go func() {
|
||||
// defer wg.Done()
|
||||
// messages := SearchStockInfo(stock, "telegram")
|
||||
// for _, message := range *messages {
|
||||
// msg = append(msg, map[string]interface{}{
|
||||
// "role": "assistant",
|
||||
// "content": message,
|
||||
// })
|
||||
// }
|
||||
//}()
|
||||
wg.Wait()
|
||||
|
||||
msg = append(msg, map[string]interface{}{
|
||||
"role": "user",
|
||||
"content": stock + "分析和总结",
|
||||
})
|
||||
|
||||
resp, err := client.R().
|
||||
SetDoNotParseResponse(true).
|
||||
SetBody(map[string]interface{}{
|
||||
"model": Model,
|
||||
"max_tokens": o.MaxTokens,
|
||||
"temperature": o.Temperature,
|
||||
"stream": true,
|
||||
"messages": msg,
|
||||
}).
|
||||
Post(apiURL)
|
||||
|
||||
if err != nil {
|
||||
ch <- err.Error()
|
||||
return
|
||||
}
|
||||
defer resp.RawBody().Close()
|
||||
|
||||
scanner := bufio.NewScanner(resp.RawBody())
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
logger.SugaredLogger.Infof("Received data: %s", line)
|
||||
if strings.HasPrefix(line, "data:") {
|
||||
data := strings.TrimPrefix(line, "data:")
|
||||
if data == "[DONE]" {
|
||||
return
|
||||
}
|
||||
|
||||
var streamResponse struct {
|
||||
Choices []struct {
|
||||
Delta struct {
|
||||
Content string `json:"content"`
|
||||
} `json:"delta"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
} `json:"choices"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(data), &streamResponse); err == nil {
|
||||
for _, choice := range streamResponse.Choices {
|
||||
if content := choice.Delta.Content; content != "" {
|
||||
ch <- content
|
||||
}
|
||||
if choice.FinishReason == "stop" {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func GetTelegraphList() *[]string {
|
||||
url := "https://www.cls.cn/telegraph"
|
||||
response, err := resty.New().R().
|
||||
SetHeader("Referer", "https://www.cls.cn/").
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
|
||||
Get(fmt.Sprintf(url))
|
||||
if err != nil {
|
||||
return &[]string{}
|
||||
}
|
||||
//logger.SugaredLogger.Info(string(response.Body()))
|
||||
document, err := goquery.NewDocumentFromReader(strings.NewReader(string(response.Body())))
|
||||
if err != nil {
|
||||
return &[]string{}
|
||||
}
|
||||
var telegraph []string
|
||||
document.Find("div.telegraph-content-box").Each(func(i int, selection *goquery.Selection) {
|
||||
//logger.SugaredLogger.Info(selection.Text())
|
||||
telegraph = append(telegraph, selection.Text())
|
||||
})
|
||||
return &telegraph
|
||||
}
|
||||
21
backend/data/openai_api_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"go-stock/backend/db"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewDeepSeekOpenAiConfig(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
ai := NewDeepSeekOpenAi()
|
||||
res := ai.NewChatStream("闻泰科技", "sh600745")
|
||||
for {
|
||||
select {
|
||||
case msg := <-res:
|
||||
if msg == "" {
|
||||
return
|
||||
}
|
||||
t.Log(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
84
backend/data/settings_api.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
gorm.Model
|
||||
TushareToken string `json:"tushareToken"`
|
||||
LocalPushEnable bool `json:"localPushEnable"`
|
||||
DingPushEnable bool `json:"dingPushEnable"`
|
||||
DingRobot string `json:"dingRobot"`
|
||||
UpdateBasicInfoOnStart bool `json:"updateBasicInfoOnStart"`
|
||||
RefreshInterval int64 `json:"refreshInterval"`
|
||||
|
||||
OpenAiEnable bool `json:"openAiEnable"`
|
||||
OpenAiBaseUrl string `json:"openAiBaseUrl"`
|
||||
OpenAiApiKey string `json:"openAiApiKey"`
|
||||
OpenAiModelName string `json:"openAiModelName"`
|
||||
OpenAiMaxTokens int `json:"openAiMaxTokens"`
|
||||
OpenAiTemperature float64 `json:"openAiTemperature"`
|
||||
Prompt string `json:"prompt"`
|
||||
}
|
||||
|
||||
func (receiver Settings) TableName() string {
|
||||
return "settings"
|
||||
}
|
||||
|
||||
type SettingsApi struct {
|
||||
Config Settings
|
||||
}
|
||||
|
||||
func NewSettingsApi(settings *Settings) *SettingsApi {
|
||||
return &SettingsApi{
|
||||
Config: *settings,
|
||||
}
|
||||
}
|
||||
|
||||
func (s SettingsApi) UpdateConfig() string {
|
||||
count := int64(0)
|
||||
db.Dao.Model(s.Config).Count(&count)
|
||||
if count > 0 {
|
||||
db.Dao.Model(s.Config).Where("id=?", s.Config.ID).Updates(map[string]any{
|
||||
"local_push_enable": s.Config.LocalPushEnable,
|
||||
"ding_push_enable": s.Config.DingPushEnable,
|
||||
"ding_robot": s.Config.DingRobot,
|
||||
"update_basic_info_on_start": s.Config.UpdateBasicInfoOnStart,
|
||||
"refresh_interval": s.Config.RefreshInterval,
|
||||
"open_ai_enable": s.Config.OpenAiEnable,
|
||||
"open_ai_base_url": s.Config.OpenAiBaseUrl,
|
||||
"open_ai_api_key": s.Config.OpenAiApiKey,
|
||||
"open_ai_model_name": s.Config.OpenAiModelName,
|
||||
"open_ai_max_tokens": s.Config.OpenAiMaxTokens,
|
||||
"open_ai_temperature": s.Config.OpenAiTemperature,
|
||||
"tushare_token": s.Config.TushareToken,
|
||||
"prompt": s.Config.Prompt,
|
||||
})
|
||||
} else {
|
||||
logger.SugaredLogger.Infof("未找到配置,创建默认配置:%+v", s.Config)
|
||||
db.Dao.Model(s.Config).Create(&Settings{
|
||||
LocalPushEnable: s.Config.LocalPushEnable,
|
||||
DingPushEnable: s.Config.DingPushEnable,
|
||||
DingRobot: s.Config.DingRobot,
|
||||
UpdateBasicInfoOnStart: s.Config.UpdateBasicInfoOnStart,
|
||||
RefreshInterval: s.Config.RefreshInterval,
|
||||
OpenAiEnable: s.Config.OpenAiEnable,
|
||||
OpenAiBaseUrl: s.Config.OpenAiBaseUrl,
|
||||
OpenAiApiKey: s.Config.OpenAiApiKey,
|
||||
OpenAiModelName: s.Config.OpenAiModelName,
|
||||
OpenAiMaxTokens: s.Config.OpenAiMaxTokens,
|
||||
OpenAiTemperature: s.Config.OpenAiTemperature,
|
||||
TushareToken: s.Config.TushareToken,
|
||||
Prompt: s.Config.Prompt,
|
||||
})
|
||||
}
|
||||
return "保存成功!"
|
||||
}
|
||||
func (s SettingsApi) GetConfig() *Settings {
|
||||
var settings Settings
|
||||
db.Dao.Model(&Settings{}).First(&settings)
|
||||
return &settings
|
||||
}
|
||||
@@ -6,11 +6,15 @@ package data
|
||||
//-----------------------------------------------------------------------------------
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/chromedp/chromedp"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
@@ -18,54 +22,69 @@ import (
|
||||
"golang.org/x/text/transform"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/plugin/soft_delete"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// http://hq.sinajs.cn/rn=1730966120830&list=sh600000,sh600859
|
||||
const sina_stook_url = "http://hq.sinajs.cn/rn=%d&list=%s"
|
||||
const tushare_api_url = "http://api.tushare.pro"
|
||||
const TushareToken = "9125ec636217a99a3218a64fc63507e95205f2666590792923cbaedf"
|
||||
const sinaStockUrl = "http://hq.sinajs.cn/rn=%d&list=%s"
|
||||
const tushareApiUrl = "http://api.tushare.pro"
|
||||
|
||||
type StockDataApi struct {
|
||||
client *resty.Client
|
||||
config *Settings
|
||||
}
|
||||
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 {
|
||||
@@ -135,6 +154,7 @@ type FollowedStock struct {
|
||||
PriceChange float64
|
||||
ChangePercent float64
|
||||
AlarmChangePercent float64
|
||||
AlarmPrice float64
|
||||
Time time.Time
|
||||
Sort int64
|
||||
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
|
||||
@@ -162,6 +182,7 @@ func (receiver StockBasic) TableName() string {
|
||||
func NewStockDataApi() *StockDataApi {
|
||||
return &StockDataApi{
|
||||
client: resty.New(),
|
||||
config: getConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,11 +194,11 @@ func (receiver StockDataApi) GetIndexBasic() {
|
||||
SetHeader("content-type", "application/json").
|
||||
SetBody(&TushareRequest{
|
||||
ApiName: "index_basic",
|
||||
Token: TushareToken,
|
||||
Token: receiver.config.TushareToken,
|
||||
Params: nil,
|
||||
Fields: fields}).
|
||||
SetResult(res).
|
||||
Post(tushare_api_url)
|
||||
Post(tushareApiUrl)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return
|
||||
@@ -217,12 +238,12 @@ func (receiver StockDataApi) GetStockBaseInfo() {
|
||||
SetHeader("content-type", "application/json").
|
||||
SetBody(&TushareRequest{
|
||||
ApiName: "stock_basic",
|
||||
Token: TushareToken,
|
||||
Token: receiver.config.TushareToken,
|
||||
Params: nil,
|
||||
Fields: fields,
|
||||
}).
|
||||
SetResult(res).
|
||||
Post(tushare_api_url)
|
||||
Post(tushareApiUrl)
|
||||
//logger.SugaredLogger.Infof("GetStockBaseInfo %s", string(resp.Body()))
|
||||
//resp.Body()写入文件
|
||||
//ioutil.WriteFile("stock_basic.json", resp.Body(), 0666)
|
||||
@@ -256,42 +277,63 @@ func (receiver StockDataApi) GetStockBaseInfo() {
|
||||
|
||||
}
|
||||
|
||||
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(sinaStockUrl, time.Now().Unix(), slice.Join(StockCodes, ",")))
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return &StockInfo{}, nil
|
||||
return &[]StockInfo{}, err
|
||||
}
|
||||
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)
|
||||
|
||||
go func() {
|
||||
var count int64
|
||||
db.Dao.Model(&StockInfo{}).Where("code = ?", stockData.Code).Count(&count)
|
||||
if count == 0 {
|
||||
db.Dao.Model(&StockInfo{}).Create(stockData)
|
||||
} else {
|
||||
db.Dao.Model(&StockInfo{}).Where("code = ?", stockData.Code).Updates(stockData)
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
return stockData, err
|
||||
|
||||
return &stockInfos, err
|
||||
}
|
||||
|
||||
func (receiver StockDataApi) Follow(stockCode string) string {
|
||||
logger.SugaredLogger.Infof("Follow %s", stockCode)
|
||||
stockInfo, err := receiver.GetStockCodeRealTimeData(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 "关注成功"
|
||||
}
|
||||
@@ -310,8 +352,11 @@ func (receiver StockDataApi) SetCostPriceAndVolume(price float64, volume int64,
|
||||
return "设置成功"
|
||||
}
|
||||
|
||||
func (receiver StockDataApi) SetAlarmChangePercent(val float64, stockCode string) string {
|
||||
err := db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", stockCode).Update("alarm_change_percent", val).Error
|
||||
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 "设置失败"
|
||||
@@ -319,6 +364,10 @@ func (receiver StockDataApi) SetAlarmChangePercent(val float64, stockCode string
|
||||
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)
|
||||
@@ -345,10 +394,10 @@ func (receiver StockDataApi) GetStockList(key string) []StockBasic {
|
||||
return result
|
||||
}
|
||||
|
||||
// GB18030 转换为 UTF8
|
||||
// GB18030ToUTF8 GB18030 转换为 UTF8
|
||||
func GB18030ToUTF8(bs []byte) string {
|
||||
reader := transform.NewReader(bytes.NewReader(bs), simplifiedchinese.GB18030.NewDecoder())
|
||||
d, err := ioutil.ReadAll(reader)
|
||||
d, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -461,3 +510,86 @@ type IndexBasic struct {
|
||||
func (IndexBasic) TableName() string {
|
||||
return "tushare_index_basic"
|
||||
}
|
||||
|
||||
func SearchStockPriceInfo(stockCode string) *[]string {
|
||||
url := "https://www.cls.cn/stock?code=" + stockCode
|
||||
// 创建一个 chromedp 上下文
|
||||
ctx, cancel := chromedp.NewContext(
|
||||
context.Background(),
|
||||
)
|
||||
defer cancel()
|
||||
var htmlContent string
|
||||
|
||||
var tasks chromedp.Tasks
|
||||
tasks = append(tasks, chromedp.Navigate(url))
|
||||
tasks = append(tasks, chromedp.WaitVisible("div.quote-change-box", chromedp.ByQuery))
|
||||
tasks = append(tasks, chromedp.ActionFunc(func(ctx context.Context) error {
|
||||
chromedp.WaitVisible("span.quote-price", chromedp.ByQuery)
|
||||
price := ""
|
||||
for {
|
||||
chromedp.Text("span.quote-price", &price, chromedp.BySearch).Do(ctx)
|
||||
logger.SugaredLogger.Infof("price:%s", price)
|
||||
if price != "" && validator.IsNumberStr(price) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}))
|
||||
tasks = append(tasks, chromedp.OuterHTML("html", &htmlContent, chromedp.ByQuery))
|
||||
|
||||
err := chromedp.Run(ctx, tasks)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return &[]string{}
|
||||
}
|
||||
document, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return &[]string{}
|
||||
}
|
||||
var messages []string
|
||||
document.Find("div.quote-text-border,span.quote-price").Each(func(i int, selection *goquery.Selection) {
|
||||
text := strutil.RemoveNonPrintable(selection.Text())
|
||||
logger.SugaredLogger.Info(text)
|
||||
messages = append(messages, text)
|
||||
|
||||
})
|
||||
return &messages
|
||||
}
|
||||
func SearchStockInfo(stock, msgType string) *[]string {
|
||||
// 创建一个 chromedp 上下文
|
||||
ctx, cancel := chromedp.NewContext(
|
||||
context.Background(),
|
||||
chromedp.WithLogf(logger.SugaredLogger.Infof),
|
||||
chromedp.WithErrorf(logger.SugaredLogger.Errorf),
|
||||
)
|
||||
defer cancel()
|
||||
var htmlContent string
|
||||
url := fmt.Sprintf("https://www.cls.cn/searchPage?keyword=%s&type=%s", stock, msgType)
|
||||
err := chromedp.Run(ctx,
|
||||
chromedp.Navigate(url),
|
||||
// 等待页面加载完成,可以根据需要调整等待时间
|
||||
//chromedp.Sleep(3*time.Second),
|
||||
chromedp.WaitVisible("div.search-content,a.search-content", chromedp.ByQuery),
|
||||
chromedp.OuterHTML("html", &htmlContent, chromedp.ByQuery),
|
||||
)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return &[]string{}
|
||||
}
|
||||
document, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Error(err.Error())
|
||||
return &[]string{}
|
||||
}
|
||||
var messages []string
|
||||
document.Find("div.search-telegraph-list,a.search-content").Each(func(i int, selection *goquery.Selection) {
|
||||
text := strutil.RemoveNonPrintable(selection.Text())
|
||||
if strings.Contains(text, stock) {
|
||||
messages = append(messages, text)
|
||||
logger.SugaredLogger.Infof("搜索到消息: %s", text)
|
||||
}
|
||||
})
|
||||
return &messages
|
||||
}
|
||||
|
||||
@@ -2,11 +2,17 @@ package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"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 +20,63 @@ import (
|
||||
// @Desc
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
func TestGetTelegraph(t *testing.T) {
|
||||
url := "https://www.cls.cn/telegraph"
|
||||
response, err := resty.New().R().
|
||||
SetHeader("Referer", "https://www.cls.cn/").
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
|
||||
Get(fmt.Sprintf(url))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
logger.SugaredLogger.Info(string(response.Body()))
|
||||
document, err := goquery.NewDocumentFromReader(strings.NewReader(string(response.Body())))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
document.Find("div.telegraph-content-box").Each(func(i int, selection *goquery.Selection) {
|
||||
text := selection.Text()
|
||||
logger.SugaredLogger.Info(text)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetTelegraphSearch(t *testing.T) {
|
||||
//url := "https://www.cls.cn/searchPage?keyword=%E9%97%BB%E6%B3%B0%E7%A7%91%E6%8A%80&type=telegram"
|
||||
messages := SearchStockInfo("闻泰科技", "depth")
|
||||
for _, message := range *messages {
|
||||
logger.SugaredLogger.Info(message)
|
||||
}
|
||||
|
||||
//https://www.cls.cn/stock?code=sh600745
|
||||
}
|
||||
func TestSearchStockPriceInfo(t *testing.T) {
|
||||
SearchStockPriceInfo("sh600745")
|
||||
}
|
||||
|
||||
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(sinaStockUrl, 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) {
|
||||
db.Init("../../data/stock.db")
|
||||
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,7 +134,7 @@ func TestReadFile(t *testing.T) {
|
||||
func TestFollowedList(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
stockDataApi := NewStockDataApi()
|
||||
t.Log(stockDataApi.GetFollowList())
|
||||
stockDataApi.GetFollowList()
|
||||
|
||||
}
|
||||
|
||||
|
||||
BIN
build/screenshot/img_10.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
build/screenshot/img_11.png
Normal file
|
After Width: | Height: | Size: 75 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 |
BIN
build/screenshot/img_6.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
build/screenshot/img_7.png
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
build/screenshot/img_8.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
build/screenshot/img_9.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
@@ -5,19 +5,19 @@
|
||||
!include "FileFunc.nsh"
|
||||
|
||||
!ifndef INFO_PROJECTNAME
|
||||
!define INFO_PROJECTNAME "{{.Name}}"
|
||||
!define INFO_PROJECTNAME "go-stock"
|
||||
!endif
|
||||
!ifndef INFO_COMPANYNAME
|
||||
!define INFO_COMPANYNAME "{{.Info.CompanyName}}"
|
||||
!define INFO_COMPANYNAME "sparkmemory"
|
||||
!endif
|
||||
!ifndef INFO_PRODUCTNAME
|
||||
!define INFO_PRODUCTNAME "{{.Info.ProductName}}"
|
||||
!define INFO_PRODUCTNAME "go-stock"
|
||||
!endif
|
||||
!ifndef INFO_PRODUCTVERSION
|
||||
!define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}"
|
||||
!define INFO_PRODUCTVERSION "1.0.0"
|
||||
!endif
|
||||
!ifndef INFO_COPYRIGHT
|
||||
!define INFO_COPYRIGHT "{{.Info.Copyright}}"
|
||||
!define INFO_COPYRIGHT "Copyright#sparkmemory@163.com"
|
||||
!endif
|
||||
!ifndef PRODUCT_EXECUTABLE
|
||||
!define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
|
||||
@@ -203,20 +203,12 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
|
||||
|
||||
!macro wails.associateFiles
|
||||
; Create file associations
|
||||
{{range .Info.FileAssociations}}
|
||||
!insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\""
|
||||
|
||||
File "..\{{.IconName}}.ico"
|
||||
{{end}}
|
||||
|
||||
!macroend
|
||||
|
||||
!macro wails.unassociateFiles
|
||||
; Delete app associations
|
||||
{{range .Info.FileAssociations}}
|
||||
!insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}"
|
||||
|
||||
Delete "$INSTDIR\{{.IconName}}.ico"
|
||||
{{end}}
|
||||
|
||||
!macroend
|
||||
|
||||
!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND
|
||||
@@ -235,15 +227,10 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
|
||||
|
||||
!macro wails.associateCustomProtocols
|
||||
; Create custom protocols associations
|
||||
{{range .Info.Protocols}}
|
||||
!insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\""
|
||||
|
||||
{{end}}
|
||||
|
||||
!macroend
|
||||
|
||||
!macro wails.unassociateCustomProtocols
|
||||
; Delete app custom protocol associations
|
||||
{{range .Info.Protocols}}
|
||||
!insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}"
|
||||
{{end}}
|
||||
|
||||
!macroend
|
||||
|
||||
754
frontend/package-lock.json
generated
@@ -9,11 +9,13 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"vue": "^3.2.25"
|
||||
"md-editor-v3": "^5.2.1",
|
||||
"vue": "^3.2.25",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
@@ -60,6 +62,360 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.18.4",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/autocomplete/-/autocomplete-6.18.4.tgz",
|
||||
"integrity": "sha512-sFAphGQIqyQZfP2ZBsSHV7xQvo9Py0rV0dW7W3IMRdS+zDuNb2l3no78CvUaWKGfzFjI4FTrLdUSj86IGb2hRA==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/commands": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/commands/-/commands-6.8.0.tgz",
|
||||
"integrity": "sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/view": "^6.27.0",
|
||||
"@lezer/common": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-angular": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-angular/-/lang-angular-0.1.3.tgz",
|
||||
"integrity": "sha512-xgeWGJQQl1LyStvndWtruUvb4SnBZDAu/gvFH/ZU+c0W25tQR8e5hq7WTwiIY2dNxnf+49mRiGI/9yxIwB6f5w==",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-html": "^6.0.0",
|
||||
"@codemirror/lang-javascript": "^6.1.2",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-cpp": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-cpp/-/lang-cpp-6.0.2.tgz",
|
||||
"integrity": "sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/cpp": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-css": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
||||
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.0.2",
|
||||
"@lezer/css": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-go": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-go/-/lang-go-6.0.1.tgz",
|
||||
"integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/go": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-html": {
|
||||
"version": "6.4.9",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
|
||||
"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/lang-css": "^6.0.0",
|
||||
"@codemirror/lang-javascript": "^6.0.0",
|
||||
"@codemirror/language": "^6.4.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/css": "^1.1.0",
|
||||
"@lezer/html": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-java": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-java/-/lang-java-6.0.1.tgz",
|
||||
"integrity": "sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/java": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
|
||||
"integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/javascript": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-json": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
|
||||
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/json": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-less": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-less/-/lang-less-6.0.2.tgz",
|
||||
"integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-css": "^6.2.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-liquid": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-liquid/-/lang-liquid-6.2.2.tgz",
|
||||
"integrity": "sha512-7Dm841fk37+JQW6j2rI1/uGkJyESrjzyhiIkaLjbbR0U6aFFQvMrJn35WxQreRMADMhzkyVkZM4467OR7GR8nQ==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/lang-html": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-markdown": {
|
||||
"version": "6.3.2",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-markdown/-/lang-markdown-6.3.2.tgz",
|
||||
"integrity": "sha512-c/5MYinGbFxYl4itE9q/rgN/sMTjOr8XL5OWnC+EaRMLfCbVUmmubTJfdgpfcSS2SCaT7b+Q+xi3l6CgoE+BsA==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.7.1",
|
||||
"@codemirror/lang-html": "^6.0.0",
|
||||
"@codemirror/language": "^6.3.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.2.1",
|
||||
"@lezer/markdown": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-php": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-php/-/lang-php-6.0.1.tgz",
|
||||
"integrity": "sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA==",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-html": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/php": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-python": {
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-python/-/lang-python-6.1.6.tgz",
|
||||
"integrity": "sha512-ai+01WfZhWqM92UqjnvorkxosZ2aq2u28kHvr+N3gu012XqY2CThD67JPMHnGceRfXPDBmn1HnyqowdpF57bNg==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.3.2",
|
||||
"@codemirror/language": "^6.8.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.2.1",
|
||||
"@lezer/python": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-rust": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-rust/-/lang-rust-6.0.1.tgz",
|
||||
"integrity": "sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/rust": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-sass": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz",
|
||||
"integrity": "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-css": "^6.2.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.0.2",
|
||||
"@lezer/sass": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-sql": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-sql/-/lang-sql-6.8.0.tgz",
|
||||
"integrity": "sha512-aGLmY4OwGqN3TdSx3h6QeA1NrvaYtF7kkoWR/+W7/JzB0gQtJ+VJxewlnE3+VImhA4WVlhmkJr109PefOOhjLg==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-vue": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz",
|
||||
"integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-html": "^6.0.0",
|
||||
"@codemirror/lang-javascript": "^6.1.2",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-wast": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz",
|
||||
"integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-xml": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz",
|
||||
"integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.4.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/xml": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-yaml": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz",
|
||||
"integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.2.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"@lezer/yaml": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.10.8",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.10.8.tgz",
|
||||
"integrity": "sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
"@lezer/common": "^1.1.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"style-mod": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language-data": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/language-data/-/language-data-6.5.1.tgz",
|
||||
"integrity": "sha512-0sWxeUSNlBr6OmkqybUTImADFUP0M3P0IiSde4nc24bz/6jIYzqYSgkOSLS+CBIoW1vU8Q9KUWXscBXeoMVC9w==",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-angular": "^0.1.0",
|
||||
"@codemirror/lang-cpp": "^6.0.0",
|
||||
"@codemirror/lang-css": "^6.0.0",
|
||||
"@codemirror/lang-go": "^6.0.0",
|
||||
"@codemirror/lang-html": "^6.0.0",
|
||||
"@codemirror/lang-java": "^6.0.0",
|
||||
"@codemirror/lang-javascript": "^6.0.0",
|
||||
"@codemirror/lang-json": "^6.0.0",
|
||||
"@codemirror/lang-less": "^6.0.0",
|
||||
"@codemirror/lang-liquid": "^6.0.0",
|
||||
"@codemirror/lang-markdown": "^6.0.0",
|
||||
"@codemirror/lang-php": "^6.0.0",
|
||||
"@codemirror/lang-python": "^6.0.0",
|
||||
"@codemirror/lang-rust": "^6.0.0",
|
||||
"@codemirror/lang-sass": "^6.0.0",
|
||||
"@codemirror/lang-sql": "^6.0.0",
|
||||
"@codemirror/lang-vue": "^0.1.1",
|
||||
"@codemirror/lang-wast": "^6.0.0",
|
||||
"@codemirror/lang-xml": "^6.0.0",
|
||||
"@codemirror/lang-yaml": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/legacy-modes": "^6.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/legacy-modes": {
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/legacy-modes/-/legacy-modes-6.4.2.tgz",
|
||||
"integrity": "sha512-HsvWu08gOIIk303eZQCal4H4t65O/qp1V4ul4zVa3MHK5FJ0gz3qz3O55FIkm+aQUcshUOjBx38t2hPiJwW5/g==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lint": {
|
||||
"version": "6.8.4",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/lint/-/lint-6.8.4.tgz",
|
||||
"integrity": "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.35.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/search": {
|
||||
"version": "6.5.8",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/search/-/search-6.5.8.tgz",
|
||||
"integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/state/-/state-6.5.1.tgz",
|
||||
"integrity": "sha512-3rA9lcwciEB47ZevqvD8qgbzhM9qMb8vCcQCNmDfVRPQG4JT9mSb0Jg8H7YjKGGQcFnLN323fj9jdnG59Kx6bg==",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.36.2",
|
||||
"resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.36.2.tgz",
|
||||
"integrity": "sha512-DZ6ONbs8qdJK0fdN7AB82CgI6tYXf4HWk1wSVa0+9bhVznCuuvhQtX8bFBoy3dv8rZSQqUd8GvhVAcielcidrA==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@css-render/plugin-bem": {
|
||||
"version": "0.15.14",
|
||||
"resolved": "https://registry.npmmirror.com/@css-render/plugin-bem/-/plugin-bem-0.15.14.tgz",
|
||||
@@ -463,6 +819,171 @@
|
||||
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@lezer/common": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/common/-/common-1.2.3.tgz",
|
||||
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||
},
|
||||
"node_modules/@lezer/cpp": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/cpp/-/cpp-1.1.2.tgz",
|
||||
"integrity": "sha512-macwKtyeUO0EW86r3xWQCzOV9/CF8imJLpJlPv3sDY57cPGeUZ8gXWOWNlJr52TVByMV3PayFQCA5SHEERDmVQ==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/css": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/css/-/css-1.1.9.tgz",
|
||||
"integrity": "sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/go": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/go/-/go-1.0.0.tgz",
|
||||
"integrity": "sha512-co9JfT3QqX1YkrMmourYw2Z8meGC50Ko4d54QEcQbEYpvdUvN4yb0NBZdn/9ertgvjsySxHsKzH3lbm3vqJ4Jw==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/highlight": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/html": {
|
||||
"version": "1.3.10",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/html/-/html-1.3.10.tgz",
|
||||
"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/java": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/java/-/java-1.1.3.tgz",
|
||||
"integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/javascript": {
|
||||
"version": "1.4.21",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/javascript/-/javascript-1.4.21.tgz",
|
||||
"integrity": "sha512-lL+1fcuxWYPURMM/oFZLEDm0XuLN128QPV+VuGtKpeaOGdcl9F2LYC3nh1S9LkPqx9M0mndZFdXCipNAZpzIkQ==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/lr": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/lr/-/lr-1.4.2.tgz",
|
||||
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/markdown": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/markdown/-/markdown-1.4.0.tgz",
|
||||
"integrity": "sha512-mk4MYeq6ZQdxgsgRAe0G7kqPRV6Desajfa14TcHoGGXIqqj1/2ARN31VFpmrXDgvXiGBWpA7RXtv0he+UdTkGw==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/highlight": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/php": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/php/-/php-1.0.2.tgz",
|
||||
"integrity": "sha512-GN7BnqtGRpFyeoKSEqxvGvhJQiI4zkgmYnDk/JIyc7H7Ifc1tkPnUn/R2R8meH3h/aBf5rzjvU8ZQoyiNDtDrA==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/python": {
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/python/-/python-1.1.15.tgz",
|
||||
"integrity": "sha512-aVQ43m2zk4FZYedCqL0KHPEUsqZOrmAvRhkhHlVPnDD1HODDyyQv5BRIuod4DadkgBEZd53vQOtXTonNbEgjrQ==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/rust": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/rust/-/rust-1.0.2.tgz",
|
||||
"integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/sass": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/sass/-/sass-1.0.7.tgz",
|
||||
"integrity": "sha512-8HLlOkuX/SMHOggI2DAsXUw38TuURe+3eQ5hiuk9QmYOUyC55B1dYEIMkav5A4IELVaW4e1T4P9WRiI5ka4mdw==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/xml": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/xml/-/xml-1.0.6.tgz",
|
||||
"integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/yaml": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/@lezer/yaml/-/yaml-1.0.3.tgz",
|
||||
"integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz",
|
||||
@@ -722,6 +1243,11 @@
|
||||
"integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@types/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.13",
|
||||
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.13.tgz",
|
||||
@@ -737,6 +1263,25 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/markdown-it": {
|
||||
"version": "14.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@types/markdown-it/-/markdown-it-14.1.2.tgz",
|
||||
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
|
||||
"dependencies": {
|
||||
"@types/linkify-it": "^5",
|
||||
"@types/mdurl": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@types/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="
|
||||
},
|
||||
"node_modules/@vavt/util": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vavt/util/-/util-2.1.0.tgz",
|
||||
"integrity": "sha512-YIfAvArSFVXmWvoF+DEGD0FhkhVNcCtVWWkfYtj76eSrwHh/wuEEFhiEubg1XLNM3tChO8FH8xJCT/hnizjgFQ=="
|
||||
},
|
||||
"node_modules/@vicons/ionicons5": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vicons/ionicons5/-/ionicons5-0.13.0.tgz",
|
||||
@@ -801,6 +1346,11 @@
|
||||
"@vue/shared": "3.5.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-api": {
|
||||
"version": "6.6.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
|
||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz",
|
||||
@@ -846,12 +1396,49 @@
|
||||
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz",
|
||||
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"node_modules/async-validator": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
|
||||
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/codemirror": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.1.tgz",
|
||||
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/commands": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/search": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
||||
},
|
||||
"node_modules/copy-to-clipboard": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
|
||||
"integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
|
||||
"dependencies": {
|
||||
"toggle-selection": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
|
||||
},
|
||||
"node_modules/css-render": {
|
||||
"version": "0.15.14",
|
||||
"resolved": "https://registry.npmmirror.com/css-render/-/css-render-0.15.14.tgz",
|
||||
@@ -868,6 +1455,11 @@
|
||||
"integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cssfilter": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmmirror.com/cssfilter/-/cssfilter-0.0.10.tgz",
|
||||
"integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
|
||||
@@ -975,6 +1567,14 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||
"dependencies": {
|
||||
"uc.micro": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
|
||||
@@ -987,6 +1587,22 @@
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "11.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.0.2.tgz",
|
||||
"integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-vue-next": {
|
||||
"version": "0.453.0",
|
||||
"resolved": "https://registry.npmmirror.com/lucide-vue-next/-/lucide-vue-next-0.453.0.tgz",
|
||||
"integrity": "sha512-5zmv83vxAs9SVoe22veDBi8Dw0Fh2F+oTngWgKnKOkrZVbZjceXLQ3tescV2boB0zlaf9R2Sd9RuUP2766xvsQ==",
|
||||
"peerDependencies": {
|
||||
"vue": ">=3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.14",
|
||||
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.14.tgz",
|
||||
@@ -995,10 +1611,81 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "^4.4.0",
|
||||
"linkify-it": "^5.0.0",
|
||||
"mdurl": "^2.0.0",
|
||||
"punycode.js": "^2.3.1",
|
||||
"uc.micro": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it-image-figures": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/markdown-it-image-figures/-/markdown-it-image-figures-2.1.1.tgz",
|
||||
"integrity": "sha512-mwXSQ2nPeVUzCMIE3HlLvjRioopiqyJLNph0pyx38yf9mpqFDhNGnMpAXF9/A2Xv0oiF2cVyg9xwfF0HNAz05g==",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"markdown-it": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it-sub": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/markdown-it-sub/-/markdown-it-sub-2.0.0.tgz",
|
||||
"integrity": "sha512-iCBKgwCkfQBRg2vApy9vx1C1Tu6D8XYo8NvevI3OlwzBRmiMtsJ2sXupBgEA7PPxiDwNni3qIUkhZ6j5wofDUA=="
|
||||
},
|
||||
"node_modules/markdown-it-sup": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/markdown-it-sup/-/markdown-it-sup-2.0.0.tgz",
|
||||
"integrity": "sha512-5VgmdKlkBd8sgXuoDoxMpiU+BiEt3I49GItBzzw7Mxq9CxvnhE/k09HFli09zgfFDRixDQDfDxi0mgBCXtaTvA=="
|
||||
},
|
||||
"node_modules/md-editor-v3": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/md-editor-v3/-/md-editor-v3-5.2.1.tgz",
|
||||
"integrity": "sha512-FPbVEO/9xovXM5OULfBPdM22LrKevEHnkjmYF0pqi34UF/Wt3d5LW/UX2DehwCTmYua6N7385K93un58K69xPA==",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-markdown": "^6.3.0",
|
||||
"@codemirror/language-data": "^6.5.1",
|
||||
"@types/markdown-it": "^14.0.1",
|
||||
"@vavt/util": "^2.1.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"lru-cache": "^11.0.1",
|
||||
"lucide-vue-next": "^0.453.0",
|
||||
"markdown-it": "^14.0.0",
|
||||
"markdown-it-image-figures": "^2.1.1",
|
||||
"markdown-it-sub": "^2.0.0",
|
||||
"markdown-it-sup": "^2.0.0",
|
||||
"medium-zoom": "^1.1.0",
|
||||
"xss": "^1.0.15"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="
|
||||
},
|
||||
"node_modules/medium-zoom": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/medium-zoom/-/medium-zoom-1.1.0.tgz",
|
||||
"integrity": "sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ=="
|
||||
},
|
||||
"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",
|
||||
@@ -1074,6 +1761,14 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode.js": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.28.1.tgz",
|
||||
@@ -1126,12 +1821,27 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/style-mod": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.2.tgz",
|
||||
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
|
||||
},
|
||||
"node_modules/toggle-selection": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
|
||||
},
|
||||
"node_modules/treemate": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmmirror.com/treemate/-/treemate-0.3.11.tgz",
|
||||
"integrity": "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="
|
||||
},
|
||||
"node_modules/vdirs": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmmirror.com/vdirs/-/vdirs-0.1.8.tgz",
|
||||
@@ -1241,6 +1951,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.0.tgz",
|
||||
"integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.6.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vueuc": {
|
||||
"version": "0.4.64",
|
||||
"resolved": "https://registry.npmmirror.com/vueuc/-/vueuc-0.4.64.tgz",
|
||||
@@ -1258,6 +1982,26 @@
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmmirror.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
|
||||
},
|
||||
"node_modules/xss": {
|
||||
"version": "1.0.15",
|
||||
"resolved": "https://registry.npmmirror.com/xss/-/xss-1.0.15.tgz",
|
||||
"integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==",
|
||||
"dependencies": {
|
||||
"commander": "^2.20.3",
|
||||
"cssfilter": "0.0.10"
|
||||
},
|
||||
"bin": {
|
||||
"xss": "bin/xss"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"vue": "^3.2.25"
|
||||
"md-editor-v3": "^5.2.1",
|
||||
"vue": "^3.2.25",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"keywords": [],
|
||||
"keywords": ["AI赋能股票分析","go-stock"],
|
||||
"author": "spark"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
92d1e03303e475c85c8fdd77128fc32c
|
||||
39a415166f03acc0270e24443a9e2445
|
||||
@@ -1,34 +1,195 @@
|
||||
<script setup>
|
||||
import stockInfo from './components/stock.vue'
|
||||
import {ref} from "vue";
|
||||
import { darkTheme } from 'naive-ui'
|
||||
import {
|
||||
EventsOn,
|
||||
Quit,
|
||||
WindowFullscreen, WindowGetPosition,
|
||||
WindowHide,
|
||||
WindowIsFullscreen, WindowSetPosition,
|
||||
WindowUnfullscreen
|
||||
} from '../wailsjs/runtime'
|
||||
import {h, ref} from "vue";
|
||||
import { RouterLink } from 'vue-router'
|
||||
import {darkTheme, NIcon, NText,} from 'naive-ui'
|
||||
import {
|
||||
SettingsOutline,
|
||||
ReorderTwoOutline,
|
||||
ExpandOutline,
|
||||
RefreshOutline, PowerOutline, BarChartOutline, MoveOutline, WalletOutline, StarOutline,
|
||||
} from '@vicons/ionicons5'
|
||||
|
||||
const content = ref('数据来源于网络,仅供参考\n投资有风险,入市需谨慎')
|
||||
const content = ref('数据来源于网络,仅供参考;投资有风险,入市需谨慎')
|
||||
const isFullscreen = ref(false)
|
||||
const activeKey = ref('stock')
|
||||
const containerRef= ref({})
|
||||
const realtimeProfit= ref(0)
|
||||
const telegraph= ref([])
|
||||
const menuOptions = ref([
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{
|
||||
to: {
|
||||
name: 'stock',
|
||||
params: {
|
||||
id: 'zh-CN'
|
||||
},
|
||||
}
|
||||
},
|
||||
{ default: () => '我的自选',}
|
||||
),
|
||||
key: 'stock',
|
||||
icon: renderIcon(StarOutline),
|
||||
children:[
|
||||
{
|
||||
label: ()=> h(NText, {type:realtimeProfit.value>0?'error':'success'},{ default: () => '当日盈亏 '+realtimeProfit.value+"¥"}),
|
||||
key: 'realtimeProfit',
|
||||
show: realtimeProfit.value,
|
||||
icon: renderIcon(WalletOutline),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{
|
||||
to: {
|
||||
name: 'settings',
|
||||
params: {
|
||||
id: 'zh-CN'
|
||||
}
|
||||
}
|
||||
},
|
||||
{ default: () => '设置' }
|
||||
),
|
||||
key: 'settings',
|
||||
icon: renderIcon(SettingsOutline),
|
||||
},
|
||||
{
|
||||
label: ()=> h("a", {
|
||||
href: '#',
|
||||
onClick: toggleFullscreen,
|
||||
title: '全屏 Ctrl+F 退出全屏 Esc',
|
||||
}, { default: () => isFullscreen.value?'取消全屏':'全屏' }),
|
||||
key: 'full',
|
||||
icon: renderIcon(ExpandOutline),
|
||||
},
|
||||
{
|
||||
label: ()=> h("a", {
|
||||
href: '#',
|
||||
onClick: WindowHide,
|
||||
title: '隐藏到托盘区 Ctrl+H',
|
||||
}, { default: () => '隐藏到托盘区' }),
|
||||
key: 'hide',
|
||||
icon: renderIcon(ReorderTwoOutline),
|
||||
},
|
||||
{
|
||||
label: ()=> h("a", {
|
||||
href: 'javascript:void(0)',
|
||||
style: 'cursor: move;',
|
||||
onClick: toggleStartMoveWindow,
|
||||
}, { default: () => '移动' }),
|
||||
key: 'move',
|
||||
icon: renderIcon(MoveOutline),
|
||||
},
|
||||
{
|
||||
label: ()=> h("a", {
|
||||
href: '#',
|
||||
onClick: Quit,
|
||||
}, { default: () => '退出程序' }),
|
||||
key: 'exit',
|
||||
icon: renderIcon(PowerOutline),
|
||||
},
|
||||
])
|
||||
function renderIcon(icon) {
|
||||
return () => h(NIcon, null, { default: () => h(icon) })
|
||||
}
|
||||
function toggleFullscreen(e) {
|
||||
//console.log(e)
|
||||
if (isFullscreen.value) {
|
||||
WindowUnfullscreen()
|
||||
//e.target.innerHTML = '全屏'
|
||||
} else {
|
||||
WindowFullscreen()
|
||||
// e.target.innerHTML = '取消全屏'
|
||||
}
|
||||
isFullscreen.value=!isFullscreen.value
|
||||
}
|
||||
const drag = ref(false)
|
||||
const lastPos= ref({x:0,y:0})
|
||||
function toggleStartMoveWindow(e) {
|
||||
drag.value=!drag.value
|
||||
lastPos.value={x:e.clientX,y:e.clientY}
|
||||
}
|
||||
function dragstart(e) {
|
||||
if (drag.value) {
|
||||
let x=e.clientX-lastPos.value.x
|
||||
let y=e.clientY-lastPos.value.y
|
||||
WindowGetPosition().then((pos) => {
|
||||
WindowSetPosition(pos.x+x,pos.y+y)
|
||||
})
|
||||
}
|
||||
}
|
||||
window.addEventListener('mousemove', dragstart)
|
||||
|
||||
EventsOn("realtime_profit",(data)=>{
|
||||
realtimeProfit.value=data
|
||||
})
|
||||
EventsOn("telegraph",(data)=>{
|
||||
telegraph.value=data
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-config-provider :theme="darkTheme">
|
||||
|
||||
|
||||
<n-config-provider :theme="darkTheme" ref="containerRef">
|
||||
<n-message-provider >
|
||||
<n-notification-provider>
|
||||
<n-modal-provider>
|
||||
<n-watermark
|
||||
:content="content"
|
||||
cross
|
||||
selectable
|
||||
:font-size="12"
|
||||
:line-height="12"
|
||||
:font-size="16"
|
||||
:line-height="16"
|
||||
:width="500"
|
||||
:height="400"
|
||||
:width="200"
|
||||
:x-offset="50"
|
||||
:y-offset="50"
|
||||
:y-offset="150"
|
||||
:rotate="-15"
|
||||
style="height: 100%"
|
||||
>
|
||||
<n-flex justify="center">
|
||||
<n-message-provider >
|
||||
<n-modal-provider>
|
||||
<stockInfo/>
|
||||
</n-modal-provider>
|
||||
</n-message-provider>
|
||||
<n-grid x-gap="12" :cols="1">
|
||||
<n-gi style="position: relative;top:1px;z-index: 19;width: 100%" v-if="telegraph.length>0">
|
||||
<n-marquee :speed="120" >
|
||||
<n-tag type="warning" v-for="item in telegraph" style="margin-right: 10px">
|
||||
{{item}}
|
||||
</n-tag>
|
||||
<!-- <n-text type="warning"> {{telegraph[0]}}</n-text>-->
|
||||
</n-marquee>
|
||||
</n-gi>
|
||||
<n-gi style="padding-bottom: 70px;padding-top: 5px">
|
||||
<RouterView />
|
||||
</n-gi>
|
||||
<n-gi style="position: fixed;bottom:0;z-index: 9;width: 100%">
|
||||
<n-card size="small">
|
||||
<n-menu style="font-size: 18px;"
|
||||
v-model:value="activeKey"
|
||||
mode="horizontal"
|
||||
:options="menuOptions"
|
||||
responsive
|
||||
/>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
</n-flex>
|
||||
</n-watermark>
|
||||
</n-modal-provider>
|
||||
</n-notification-provider>
|
||||
</n-message-provider>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
<style>
|
||||
|
||||
194
frontend/src/components/settings.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<script setup>
|
||||
|
||||
import {onMounted, ref, watch} from "vue";
|
||||
import {GetConfig, SendDingDingMessageByType, UpdateConfig} from "../../wailsjs/go/main/App";
|
||||
import {useMessage} from "naive-ui";
|
||||
import {data} from "../../wailsjs/go/models";
|
||||
const message = useMessage()
|
||||
|
||||
const formRef = ref(null)
|
||||
const formValue = ref({
|
||||
ID:1,
|
||||
tushareToken:'',
|
||||
dingPush:{
|
||||
enable:false,
|
||||
dingRobot: ''
|
||||
},
|
||||
localPush:{
|
||||
enable:true,
|
||||
},
|
||||
updateBasicInfoOnStart:false,
|
||||
refreshInterval:1,
|
||||
openAI:{
|
||||
enable:false,
|
||||
baseUrl: 'https://api.deepseek.com',
|
||||
apiKey: '',
|
||||
model: 'deepseek-chat',
|
||||
temperature: 0.1,
|
||||
maxTokens: 1024,
|
||||
prompt:"",
|
||||
},
|
||||
})
|
||||
|
||||
onMounted(()=>{
|
||||
GetConfig().then(res=>{
|
||||
formValue.value.ID = res.ID
|
||||
formValue.value.tushareToken = res.tushareToken
|
||||
formValue.value.dingPush = {
|
||||
enable:res.dingPushEnable,
|
||||
dingRobot:res.dingRobot
|
||||
}
|
||||
formValue.value.localPush = {
|
||||
enable:res.localPushEnable,
|
||||
}
|
||||
formValue.value.updateBasicInfoOnStart = res.updateBasicInfoOnStart
|
||||
formValue.value.refreshInterval = res.refreshInterval
|
||||
formValue.value.openAI = {
|
||||
enable:res.openAiEnable,
|
||||
baseUrl: res.openAiBaseUrl,
|
||||
apiKey:res.openAiApiKey,
|
||||
model:res.openAiModelName,
|
||||
temperature:res.openAiTemperature,
|
||||
maxTokens:res.openAiMaxTokens,
|
||||
prompt:res.prompt,
|
||||
}
|
||||
console.log(res)
|
||||
})
|
||||
//message.info("加载完成")
|
||||
})
|
||||
|
||||
|
||||
function saveConfig(){
|
||||
let config= new data.Settings({
|
||||
ID:formValue.value.ID,
|
||||
dingPushEnable:formValue.value.dingPush.enable,
|
||||
dingRobot:formValue.value.dingPush.dingRobot,
|
||||
localPushEnable:formValue.value.localPush.enable,
|
||||
updateBasicInfoOnStart:formValue.value.updateBasicInfoOnStart,
|
||||
refreshInterval:formValue.value.refreshInterval,
|
||||
openAiEnable:formValue.value.openAI.enable,
|
||||
openAiBaseUrl:formValue.value.openAI.baseUrl,
|
||||
openAiApiKey:formValue.value.openAI.apiKey,
|
||||
openAiModelName:formValue.value.openAI.model,
|
||||
openAiMaxTokens:formValue.value.openAI.maxTokens,
|
||||
openAiTemperature:formValue.value.openAI.temperature,
|
||||
tushareToken:formValue.value.tushareToken,
|
||||
prompt:formValue.value.openAI.prompt
|
||||
})
|
||||
|
||||
//console.log("Settings",config)
|
||||
UpdateConfig(config).then(res=>{
|
||||
message.success(res)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function getHeight() {
|
||||
return document.documentElement.clientHeight
|
||||
}
|
||||
function sendTestNotice(){
|
||||
let markdown="### go-stock test\n"+new Date()
|
||||
let msg='{' +
|
||||
' "msgtype": "markdown",' +
|
||||
' "markdown": {' +
|
||||
' "title":"go-stock'+new Date()+'",' +
|
||||
' "text": "'+markdown+'"' +
|
||||
' },' +
|
||||
' "at": {' +
|
||||
' "isAtAll": true' +
|
||||
' }' +
|
||||
' }'
|
||||
|
||||
SendDingDingMessageByType(msg, "test-"+new Date().getTime(),1).then(res=>{
|
||||
message.info(res)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-flex justify="left" style="margin-top: 12px;padding-left: 12px">
|
||||
<n-form ref="formRef" :model="formValue" :label-placement="'left'" :label-align="'left'" style="width: 100%;height: 100%">
|
||||
<n-grid :cols="24" :x-gap="24" style="text-align: left">
|
||||
<n-gi :span="24">
|
||||
<n-text type="default" style="font-size: 25px;font-weight: bold">基础设置</n-text>
|
||||
</n-gi>
|
||||
<n-form-item-gi :span="10" label="Tushare api token:" path="tushareToken" >
|
||||
<n-input type="text" placeholder="Tushare api token" v-model:value="formValue.tushareToken" clearable />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="6" label="启动时更新A股/指数信息:" path="updateBasicInfoOnStart" >
|
||||
<n-switch v-model:value="formValue.updateBasicInfoOnStart" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="6" label="数据刷新间隔(重启生效):" path="refreshInterval" >
|
||||
<n-input-number v-model:value="formValue.refreshInterval" placeholder="请输入数据刷新间隔(秒)">
|
||||
<template #suffix>
|
||||
秒
|
||||
</template>
|
||||
</n-input-number>
|
||||
</n-form-item-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-grid :cols="24" :x-gap="24" style="text-align: left">
|
||||
<n-gi :span="24">
|
||||
<n-text type="default" style="font-size: 25px;font-weight: bold">通知设置</n-text>
|
||||
</n-gi>
|
||||
<n-form-item-gi :span="6" label="是否启用钉钉推送:" path="dingPush.enable" >
|
||||
<n-switch v-model:value="formValue.dingPush.enable" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="6" label="是否启用本地推送:" path="localPush.enable" >
|
||||
<n-switch v-model:value="formValue.localPush.enable" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="22" v-if="formValue.dingPush.enable" label="钉钉机器人接口地址:" path="dingPush.dingRobot" >
|
||||
<n-input placeholder="请输入钉钉机器人接口地址" v-model:value="formValue.dingPush.dingRobot"/>
|
||||
<n-button type="primary" @click="sendTestNotice">发送测试通知</n-button>
|
||||
</n-form-item-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-grid :cols="24" :x-gap="24" style="text-align: left;">
|
||||
<n-gi :span="24">
|
||||
<n-text type="default" style="font-size: 25px;font-weight: bold">OpenAI设置</n-text>
|
||||
</n-gi>
|
||||
<n-form-item-gi :span="6" label="是否启用AI诊股:" path="openAI.enable" >
|
||||
<n-switch v-model:value="formValue.openAI.enable" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="22" v-if="formValue.openAI.enable" label="openAI 接口地址:" path="openAI.baseUrl">
|
||||
<n-input type="text" placeholder="AI接口地址" v-model:value="formValue.openAI.baseUrl" clearable />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="10" v-if="formValue.openAI.enable" label="openAI apiKey:" path="openAI.apiKey">
|
||||
<n-input type="text" placeholder="apiKey" v-model:value="formValue.openAI.apiKey" clearable />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="12" v-if="formValue.openAI.enable" label="AI模型:" path="openAI.model">
|
||||
<n-input type="text" placeholder="AI模型" v-model:value="formValue.openAI.model" clearable />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="12" v-if="formValue.openAI.enable" label="temperature:" path="openAI.temperature" >
|
||||
<n-input-number placeholder="temperature" v-model:value="formValue.openAI.temperature"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="10" v-if="formValue.openAI.enable" label="maxTokens:" path="openAI.maxTokens">
|
||||
<n-input-number placeholder="maxTokens" v-model:value="formValue.openAI.maxTokens"/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="22" v-if="formValue.openAI.enable" label="自定义Prompt:" path="openAI.prompt">
|
||||
<n-input v-model:value="formValue.openAI.prompt"
|
||||
type="textarea"
|
||||
:show-count="true"
|
||||
placeholder="请输入prompt"
|
||||
:autosize="{
|
||||
minRows: 5,
|
||||
maxRows: 8
|
||||
}"
|
||||
/>
|
||||
</n-form-item-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-gi :span="24">
|
||||
<div style="display: flex; justify-content: center">
|
||||
<n-button type="primary" @click="saveConfig">
|
||||
保存
|
||||
</n-button>
|
||||
</div>
|
||||
</n-gi>
|
||||
</n-form>
|
||||
</n-flex>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,20 +1,25 @@
|
||||
<script setup>
|
||||
import {onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue'
|
||||
import {computed, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue'
|
||||
import {
|
||||
Greet,
|
||||
Follow,
|
||||
UnFollow,
|
||||
Follow, GetConfig,
|
||||
GetFollowList,
|
||||
GetStockList,
|
||||
SetCostPriceAndVolume,
|
||||
SendDingDingMessage, SetAlarmChangePercent
|
||||
Greet, NewChat, NewChatStream,
|
||||
SendDingDingMessage, SendDingDingMessageByType,
|
||||
SetAlarmChangePercent,
|
||||
SetCostPriceAndVolume, SetStockSort,
|
||||
UnFollow
|
||||
} from '../../wailsjs/go/main/App'
|
||||
import {NButton, NFlex, NForm, NFormItem, NInputNumber, NText, useMessage, useModal} from 'naive-ui'
|
||||
import { WindowFullscreen,WindowUnfullscreen,EventsOn } from '../../wailsjs/runtime'
|
||||
import {Add, StarOutline} from '@vicons/ionicons5'
|
||||
|
||||
import {NButton, NFlex, NForm, NFormItem, NInputNumber, NText, useMessage, useModal,useNotification} from 'naive-ui'
|
||||
import {EventsOn, WindowFullscreen, WindowReload, WindowUnfullscreen} from '../../wailsjs/runtime'
|
||||
import {Add, Search,StarOutline} from '@vicons/ionicons5'
|
||||
import { MdPreview } from 'md-editor-v3';
|
||||
// preview.css相比style.css少了编辑器那部分样式
|
||||
import 'md-editor-v3/lib/preview.css';
|
||||
const mdPreviewRef = ref(null)
|
||||
const message = useMessage()
|
||||
const modal = useModal()
|
||||
const notify = useNotification()
|
||||
|
||||
const stocks=ref([])
|
||||
const results=ref({})
|
||||
@@ -25,6 +30,7 @@ const options=ref([])
|
||||
const modalShow = ref(false)
|
||||
const modalShow2 = ref(false)
|
||||
const modalShow3 = ref(false)
|
||||
const modalShow4 = ref(false)
|
||||
const addBTN = ref(true)
|
||||
const formModel = ref({
|
||||
name: "",
|
||||
@@ -32,6 +38,8 @@ const formModel = ref({
|
||||
costPrice: 0.000,
|
||||
volume: 0,
|
||||
alarm: 0,
|
||||
alarmPrice:0,
|
||||
sort:999,
|
||||
})
|
||||
|
||||
const data = reactive({
|
||||
@@ -41,8 +49,20 @@ const data = reactive({
|
||||
kURL:"",
|
||||
resultText: "Please enter your name below 👇",
|
||||
fullscreen: false,
|
||||
airesult: "",
|
||||
openAiEnable: false,
|
||||
loading: true,
|
||||
})
|
||||
|
||||
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 => {
|
||||
@@ -62,25 +82,29 @@ onBeforeMount(()=>{
|
||||
}
|
||||
}
|
||||
monitor()
|
||||
message.destroyAll
|
||||
message.destroyAll()
|
||||
})
|
||||
GetConfig().then(result => {
|
||||
if (result.openAiEnable) {
|
||||
data.openAiEnable = true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
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()
|
||||
}
|
||||
}, 3500)
|
||||
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
console.log(`the component is now unmounted.`)
|
||||
// console.log(`the component is now unmounted.`)
|
||||
clearInterval(ticker.value)
|
||||
})
|
||||
|
||||
@@ -92,21 +116,40 @@ EventsOn("showSearch",(data)=>{
|
||||
addBTN.value = data === 1;
|
||||
})
|
||||
|
||||
EventsOn("stock_price",(data)=>{
|
||||
//console.log("stock_price",data['股票代码'])
|
||||
updateData(data)
|
||||
})
|
||||
|
||||
EventsOn("refreshFollowList",(data)=>{
|
||||
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
|
||||
})
|
||||
|
||||
WindowReload()
|
||||
// message.loading("refresh...")
|
||||
// GetFollowList().then(result => {
|
||||
// followList.value = result
|
||||
// for (const followedStock of result) {
|
||||
// if (!stocks.value.includes(followedStock.StockCode)) {
|
||||
// stocks.value.push(followedStock.StockCode)
|
||||
// }
|
||||
// }
|
||||
// monitor()
|
||||
// message.destroyAll
|
||||
// })
|
||||
})
|
||||
|
||||
EventsOn("newChatStream",async (msg) => {
|
||||
//console.log("newChatStream:->",data.airesult)
|
||||
data.loading = false
|
||||
if (msg === "DONE") {
|
||||
message.info("AI分析完成!")
|
||||
message.destroyAll()
|
||||
} else {
|
||||
data.airesult = data.airesult + msg
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
//判断是否是A股交易时间
|
||||
function isTradingTime() {
|
||||
const now = new Date();
|
||||
@@ -141,19 +184,24 @@ function AddStock(){
|
||||
|
||||
|
||||
|
||||
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(value){
|
||||
console.log("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,
|
||||
@@ -165,54 +213,66 @@ function getStockList(value){
|
||||
}
|
||||
}
|
||||
|
||||
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 => {
|
||||
if(result["当前价格"]<=0){
|
||||
result["当前价格"]=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.Sort=res[0].Sort
|
||||
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"
|
||||
}
|
||||
if(res[0].AlarmChangePercent>0&&Math.abs(roundedNum)>res[0].AlarmChangePercent){
|
||||
SendMessage(result)
|
||||
}
|
||||
}
|
||||
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) {
|
||||
console.log("onSelect",item)
|
||||
//console.log("onSelect",item)
|
||||
|
||||
if(item.indexOf("-")>0){
|
||||
item=item.split("-")[1].toLowerCase()
|
||||
@@ -225,17 +285,24 @@ function onSelect(item) {
|
||||
|
||||
function search(code,name){
|
||||
setTimeout(() => {
|
||||
window.open("https://xueqiu.com/S/"+code)
|
||||
//window.open("https://xueqiu.com/S/"+code)
|
||||
//window.open("https://www.cls.cn/stock?code="+code)
|
||||
//window.open("https://quote.eastmoney.com/"+code+".html")
|
||||
//window.open("https://finance.sina.com.cn/realstock/company/"+code+"/nc.shtml")
|
||||
window.open("https://www.iwencai.com/unifiedwap/result?w="+code)
|
||||
//window.open("https://www.iwencai.com/chat/?question="+code)
|
||||
}, 500)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -253,10 +320,16 @@ function showK(code,name){
|
||||
}
|
||||
|
||||
|
||||
function updateCostPriceAndVolumeNew(code,price,volume,alarm){
|
||||
console.log(code,price,volume)
|
||||
if(alarm){
|
||||
SetAlarmChangePercent(alarm,code).then(result => {
|
||||
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)
|
||||
})
|
||||
}
|
||||
@@ -271,7 +344,7 @@ function updateCostPriceAndVolumeNew(code,price,volume,alarm){
|
||||
}
|
||||
}
|
||||
monitor()
|
||||
message.destroyAll
|
||||
message.destroyAll()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -285,50 +358,95 @@ function fullscreen(){
|
||||
data.fullscreen=!data.fullscreen
|
||||
}
|
||||
|
||||
function SendMessage(result){
|
||||
|
||||
//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市场行情\n\n"+
|
||||
let markdown="### go-stock ["+typeName+"]\n\n"+
|
||||
"### "+result["股票名称"]+"("+result["股票代码"]+")\n" +
|
||||
"- 当前价格: "+result["当前价格"]+" "+result.s+"\n" +
|
||||
"- 当前价格: "+result["当前价格"]+" "+result.changePercent+"%\n" +
|
||||
"- 最高价: "+result["今日最高价"]+" "+result.highRate+"\n" +
|
||||
"- 最低价: "+result["今日最低价"]+" "+result.lowRate+"\n" +
|
||||
"- 昨收价: "+result["昨日收盘价"]+"\n" +
|
||||
"- 今开价: "+result["今日开盘价"]+"\n" +
|
||||
"- 成本价: "+result.costPrice+" "+result.profit+"% "+result.profitAmount+" ¥\n" +
|
||||
"- 成本数量: "+result.volume+"股\n" +
|
||||
"- 成本数量: "+result.costVolume+"股\n" +
|
||||
"- 日期: "+result["日期"]+" "+result["时间"]+"\n\n"+
|
||||
"\n"
|
||||
let title=result["股票名称"]+"("+result["股票代码"]+") "+result["当前价格"]+" "+result.changePercent
|
||||
|
||||
let msg='{' +
|
||||
' "msgtype": "markdown",' +
|
||||
' "markdown": {' +
|
||||
' "title":"'+result["股票名称"]+"("+result["股票代码"]+") "+result["当前价格"]+" "+result.s+'",' +
|
||||
' "title":"['+typeName+"]"+title+'",' +
|
||||
' "text": "'+markdown+'"' +
|
||||
' },' +
|
||||
' "at": {' +
|
||||
' "isAtAll": true' +
|
||||
' }' +
|
||||
' }'
|
||||
SendDingDingMessage(msg,result["股票代码"])
|
||||
// SendDingDingMessage(msg,result["股票代码"])
|
||||
SendDingDingMessageByType(msg,result["股票代码"],type)
|
||||
}
|
||||
|
||||
function aiCheckStock(stock,stockCode){
|
||||
data.airesult=""
|
||||
data.name=stock
|
||||
data.code=stockCode
|
||||
data.loading=true
|
||||
modalShow4.value=true
|
||||
message.loading("ai检测中...",{
|
||||
duration: 0,
|
||||
})
|
||||
NewChatStream(stock,stockCode)
|
||||
}
|
||||
|
||||
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 :data-code="result['股票代码']" :bordered="false" :title="result['股票名称']" :closable="true" @close="removeMonitor(result['股票代码'],result['股票名称'])">
|
||||
<n-gi v-for="result in sortedResults" >
|
||||
<n-card :data-code="result['股票代码']" :bordered="false" :title="result['股票名称']" :closable="false" @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-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-text :type="'info'">{{"最高 "+result["今日最高价"]+" "+result.highRate }}%</n-text>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-text :type="'info'">{{"最低 "+result["今日最低价"]+" "+result.lowRate }}</n-text>
|
||||
<n-text :type="'info'">{{"最低 "+result["今日最低价"]+" "+result.lowRate }}%</n-text>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-text :type="'info'">{{"昨收 "+result["昨日收盘价"]}}</n-text>
|
||||
@@ -338,12 +456,16 @@ function SendMessage(result){
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<template #header-extra>
|
||||
<!-- <n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{result.volume+"股"}}</n-tag>-->
|
||||
<n-button size="tiny" secondary type="primary" @click="removeMonitor(result['股票代码'],result['股票名称'],result.key)">
|
||||
取消关注
|
||||
</n-button>
|
||||
<n-button size="tiny" v-if="data.openAiEnable" secondary type="warning" @click="aiCheckStock(result['股票名称'],result['股票代码'])"> AI分析 </n-button>
|
||||
|
||||
</template>
|
||||
<template #footer>
|
||||
<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.profit+"%"+" ( "+result.profitAmount+" ¥ )"}}</n-tag>
|
||||
<n-tag size="small" v-if="result.costPrice>0" :type="result.profitType">{{"成本:"+result.costPrice+"*"+result.costVolume+" "+result.profit+"%"+" ( "+result.profitAmount+" ¥ )"}}</n-tag>
|
||||
</n-flex>
|
||||
</template>
|
||||
<template #action>
|
||||
@@ -353,37 +475,40 @@ function SendMessage(result){
|
||||
<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-affix :trigger-bottom="60" v-if="addBTN">
|
||||
<div style="position: fixed;bottom: 18px;right:0;z-index: 10;width: 350px">
|
||||
<!-- <n-card :bordered="false">-->
|
||||
<n-input-group>
|
||||
|
||||
<n-button type="info" @click="addBTN=false" >隐藏</n-button>
|
||||
<n-auto-complete v-model:value="data.name"
|
||||
<!-- <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">
|
||||
<n-button type="primary" @click="AddStock" v-if="addBTN">
|
||||
<n-icon :component="Add"/> 关注该股票
|
||||
</n-button>
|
||||
</n-input-group>
|
||||
<!-- </n-card>-->
|
||||
|
||||
</n-affix>
|
||||
</div>
|
||||
<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: '请输入数量'},alarm:{required: true, message: '涨跌报警值'} }" label-placement="left" label-width="80px">
|
||||
<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>
|
||||
@@ -401,9 +526,20 @@ function SendMessage(result){
|
||||
</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,formModel.alarm)">保存</n-button>
|
||||
<n-button type="primary" @click="updateCostPriceAndVolumeNew(formModel.code,formModel.costPrice,formModel.volume,formModel.alarm,formModel)">保存</n-button>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
@@ -413,28 +549,14 @@ function SendMessage(result){
|
||||
<n-modal v-model:show="modalShow3" :title="data.name" style="width: 600px" :preset="'card'">
|
||||
<n-image :src="data.kURL" />
|
||||
</n-modal>
|
||||
|
||||
<n-modal transform-origin="center" v-model:show="modalShow4" preset="card" style="width: 800px;height: 480px" :title="'['+data.name+']AI分析结果'" >
|
||||
<n-spin size="small" :show="data.loading">
|
||||
<MdPreview ref="mdPreviewRef" style="height: 380px" :modelValue="data.airesult" :theme="'dark'"/>
|
||||
</n-spin>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.result {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
margin: 1.5rem auto;
|
||||
}
|
||||
.input-box {
|
||||
text-align: center;
|
||||
}
|
||||
.input {
|
||||
width: 200px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.light-green {
|
||||
height: 108px;
|
||||
background-color: rgba(0, 128, 0, 0.12);
|
||||
}
|
||||
.green {
|
||||
height: 108px;
|
||||
background-color: rgba(0, 128, 0, 0.24);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import {createApp} from 'vue'
|
||||
import naive from 'naive-ui'
|
||||
import App from './App.vue'
|
||||
import router from './router/router'
|
||||
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
app.use(naive)
|
||||
|
||||
app.mount('#app')
|
||||
16
frontend/src/router/router.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createMemoryHistory, createRouter } from 'vue-router'
|
||||
|
||||
import stockView from '../components/stock.vue'
|
||||
import settingsView from '../components/settings.vue'
|
||||
|
||||
const routes = [
|
||||
{ path: '/', component: stockView,name: 'stock' },
|
||||
{ path: '/settings/:id', component: settingsView,name: 'settings' },
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
export default router
|
||||
14
frontend/wailsjs/go/main/App.d.ts
vendored
@@ -4,16 +4,28 @@ import {data} from '../models';
|
||||
|
||||
export function Follow(arg1:string):Promise<string>;
|
||||
|
||||
export function GetConfig():Promise<data.Settings>;
|
||||
|
||||
export function GetFollowList():Promise<Array<data.FollowedStock>>;
|
||||
|
||||
export function GetStockList(arg1:string):Promise<Array<data.StockBasic>>;
|
||||
|
||||
export function Greet(arg1:string):Promise<data.StockInfo>;
|
||||
|
||||
export function NewChat(arg1:string):Promise<string>;
|
||||
|
||||
export function NewChatStream(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
export function SendDingDingMessage(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function SetAlarmChangePercent(arg1:number,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>;
|
||||
|
||||
export function UpdateConfig(arg1:data.Settings):Promise<string>;
|
||||
|
||||
@@ -6,6 +6,10 @@ export function Follow(arg1) {
|
||||
return window['go']['main']['App']['Follow'](arg1);
|
||||
}
|
||||
|
||||
export function GetConfig() {
|
||||
return window['go']['main']['App']['GetConfig']();
|
||||
}
|
||||
|
||||
export function GetFollowList() {
|
||||
return window['go']['main']['App']['GetFollowList']();
|
||||
}
|
||||
@@ -18,18 +22,38 @@ export function Greet(arg1) {
|
||||
return window['go']['main']['App']['Greet'](arg1);
|
||||
}
|
||||
|
||||
export function NewChat(arg1) {
|
||||
return window['go']['main']['App']['NewChat'](arg1);
|
||||
}
|
||||
|
||||
export function NewChatStream(arg1, arg2) {
|
||||
return window['go']['main']['App']['NewChatStream'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function SendDingDingMessage(arg1, arg2) {
|
||||
return window['go']['main']['App']['SendDingDingMessage'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function SetAlarmChangePercent(arg1, arg2) {
|
||||
return window['go']['main']['App']['SetAlarmChangePercent'](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);
|
||||
}
|
||||
|
||||
export function UpdateConfig(arg1) {
|
||||
return window['go']['main']['App']['UpdateConfig'](arg1);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export namespace data {
|
||||
PriceChange: number;
|
||||
ChangePercent: number;
|
||||
AlarmChangePercent: number;
|
||||
AlarmPrice: number;
|
||||
// Go type: time
|
||||
Time: any;
|
||||
Sort: number;
|
||||
@@ -28,6 +29,7 @@ export namespace data {
|
||||
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"];
|
||||
@@ -51,6 +53,71 @@ export namespace data {
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class Settings {
|
||||
ID: number;
|
||||
// Go type: time
|
||||
CreatedAt: any;
|
||||
// Go type: time
|
||||
UpdatedAt: any;
|
||||
// Go type: gorm
|
||||
DeletedAt: any;
|
||||
tushareToken: string;
|
||||
localPushEnable: boolean;
|
||||
dingPushEnable: boolean;
|
||||
dingRobot: string;
|
||||
updateBasicInfoOnStart: boolean;
|
||||
refreshInterval: number;
|
||||
openAiEnable: boolean;
|
||||
openAiBaseUrl: string;
|
||||
openAiApiKey: string;
|
||||
openAiModelName: string;
|
||||
openAiMaxTokens: number;
|
||||
openAiTemperature: number;
|
||||
prompt: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new Settings(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.tushareToken = source["tushareToken"];
|
||||
this.localPushEnable = source["localPushEnable"];
|
||||
this.dingPushEnable = source["dingPushEnable"];
|
||||
this.dingRobot = source["dingRobot"];
|
||||
this.updateBasicInfoOnStart = source["updateBasicInfoOnStart"];
|
||||
this.refreshInterval = source["refreshInterval"];
|
||||
this.openAiEnable = source["openAiEnable"];
|
||||
this.openAiBaseUrl = source["openAiBaseUrl"];
|
||||
this.openAiApiKey = source["openAiApiKey"];
|
||||
this.openAiModelName = source["openAiModelName"];
|
||||
this.openAiMaxTokens = source["openAiMaxTokens"];
|
||||
this.openAiTemperature = source["openAiTemperature"];
|
||||
this.prompt = source["prompt"];
|
||||
}
|
||||
|
||||
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 StockBasic {
|
||||
ID: number;
|
||||
// Go type: time
|
||||
@@ -136,13 +203,14 @@ export namespace data {
|
||||
"时间": string;
|
||||
"股票代码": string;
|
||||
"股票名称": string;
|
||||
"上次当前价格": number;
|
||||
"当前价格": string;
|
||||
"成交的股票数": string;
|
||||
"成交金额": string;
|
||||
"今日开盘价": string;
|
||||
"昨日收盘价": string;
|
||||
"今日最低价": string;
|
||||
"今日最高价": string;
|
||||
"今日最低价": string;
|
||||
"竞买价": string;
|
||||
"竞卖价": string;
|
||||
"买一报价": string;
|
||||
@@ -165,6 +233,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);
|
||||
@@ -180,13 +260,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["买一报价"];
|
||||
@@ -209,6 +290,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 {
|
||||
|
||||
22
go.mod
@@ -1,18 +1,21 @@
|
||||
module go-stock
|
||||
|
||||
go 1.21
|
||||
go 1.23
|
||||
|
||||
toolchain go1.23.0
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.10.1
|
||||
github.com/chromedp/chromedp v0.11.2
|
||||
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
|
||||
golang.org/x/text v0.21.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gorm.io/gorm v1.25.12
|
||||
gorm.io/plugin/dbresolver v1.5.3
|
||||
@@ -20,8 +23,11 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb // indirect
|
||||
github.com/chromedp/sysutil v1.1.0 // 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
|
||||
@@ -32,19 +38,25 @@ require (
|
||||
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/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.4.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
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/labstack/echo/v4 v4.10.2 // indirect
|
||||
github.com/labstack/gommon v0.4.0 // indirect
|
||||
github.com/leaanthony/go-ansi-parser v1.6.0 // indirect
|
||||
github.com/leaanthony/gosod v1.0.3 // indirect
|
||||
github.com/leaanthony/slicer v1.6.0 // indirect
|
||||
github.com/leaanthony/u v1.1.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // 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
|
||||
@@ -57,10 +69,10 @@ require (
|
||||
github.com/wailsapp/go-webview2 v1.0.16 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
|
||||
74
go.sum
@@ -1,7 +1,17 @@
|
||||
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
|
||||
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
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/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb h1:noKVm2SsG4v0Yd0lHNtFYc9EUxIVvrr4kJ6hM8wvIYU=
|
||||
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM=
|
||||
github.com/chromedp/chromedp v0.11.2 h1:ZRHTh7DjbNTlfIv3NFTbB7eVeu5XCNkgrpcGSpn2oX0=
|
||||
github.com/chromedp/chromedp v0.11.2/go.mod h1:lr8dFRLKsdTTWb75C/Ttol2vnBKOSnt0BW8R9Xaupi8=
|
||||
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
|
||||
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
||||
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=
|
||||
@@ -37,8 +47,17 @@ github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ
|
||||
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/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||
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/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
@@ -51,6 +70,8 @@ github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/
|
||||
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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
|
||||
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
|
||||
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
|
||||
@@ -66,8 +87,12 @@ 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/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
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=
|
||||
@@ -80,6 +105,10 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
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/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
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=
|
||||
@@ -124,22 +153,36 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
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=
|
||||
@@ -157,25 +200,40 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
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=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
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/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
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=
|
||||
|
||||
81
main.go
@@ -16,6 +16,7 @@ import (
|
||||
"go-stock/backend/db"
|
||||
"log"
|
||||
"os"
|
||||
goruntime "runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -39,12 +40,12 @@ func main() {
|
||||
db.Dao.AutoMigrate(&data.StockBasic{})
|
||||
db.Dao.AutoMigrate(&data.FollowedStock{})
|
||||
db.Dao.AutoMigrate(&data.IndexBasic{})
|
||||
db.Dao.AutoMigrate(&data.Settings{})
|
||||
|
||||
if stocksBin != nil && len(stocksBin) > 0 {
|
||||
go initStockData()
|
||||
}
|
||||
go data.NewStockDataApi().GetStockBaseInfo()
|
||||
go data.NewStockDataApi().GetIndexBasic()
|
||||
updateBasicInfo()
|
||||
|
||||
// Create an instance of the app structure
|
||||
app := NewApp()
|
||||
@@ -63,42 +64,51 @@ func main() {
|
||||
FileMenu.AddSeparator()
|
||||
FileMenu.AddText("窗口全屏", keys.CmdOrCtrl("f"), func(callback *menu.CallbackData) {
|
||||
runtime.WindowFullscreen(app.ctx)
|
||||
callback.MenuItem.Hide()
|
||||
})
|
||||
FileMenu.AddText("窗口还原", keys.Key("Esc"), func(callback *menu.CallbackData) {
|
||||
runtime.WindowUnfullscreen(app.ctx)
|
||||
})
|
||||
FileMenu.AddText("隐藏到托盘区", keys.CmdOrCtrl("h"), func(_ *menu.CallbackData) {
|
||||
runtime.Hide(app.ctx)
|
||||
})
|
||||
FileMenu.AddText("退出", keys.CmdOrCtrl("q"), func(_ *menu.CallbackData) {
|
||||
runtime.Quit(app.ctx)
|
||||
})
|
||||
|
||||
if goruntime.GOOS == "windows" {
|
||||
FileMenu.AddText("隐藏到托盘区", keys.CmdOrCtrl("h"), func(_ *menu.CallbackData) {
|
||||
runtime.WindowHide(app.ctx)
|
||||
})
|
||||
|
||||
FileMenu.AddText("显示", keys.CmdOrCtrl("v"), func(_ *menu.CallbackData) {
|
||||
runtime.WindowShow(app.ctx)
|
||||
})
|
||||
}
|
||||
|
||||
//FileMenu.AddText("退出", keys.CmdOrCtrl("q"), func(_ *menu.CallbackData) {
|
||||
// runtime.Quit(app.ctx)
|
||||
//})
|
||||
|
||||
// Create application with options
|
||||
err := wails.Run(&options.App{
|
||||
Title: "go-stock",
|
||||
Width: 1366,
|
||||
Height: 860,
|
||||
MinWidth: 1024,
|
||||
MinHeight: 768,
|
||||
MaxWidth: 1280,
|
||||
MaxHeight: 960,
|
||||
DisableResize: false,
|
||||
Fullscreen: false,
|
||||
Frameless: false,
|
||||
StartHidden: false,
|
||||
HideWindowOnClose: false,
|
||||
BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255},
|
||||
Assets: assets,
|
||||
Menu: AppMenu,
|
||||
Logger: nil,
|
||||
LogLevel: logger.DEBUG,
|
||||
OnStartup: app.startup,
|
||||
OnDomReady: app.domReady,
|
||||
OnBeforeClose: app.beforeClose,
|
||||
OnShutdown: app.shutdown,
|
||||
WindowStartState: options.Normal,
|
||||
Title: "go-stock",
|
||||
Width: 1366,
|
||||
Height: 920,
|
||||
MinWidth: 1024,
|
||||
MinHeight: 768,
|
||||
MaxWidth: 1920,
|
||||
MaxHeight: 960,
|
||||
DisableResize: false,
|
||||
Fullscreen: false,
|
||||
Frameless: true,
|
||||
StartHidden: false,
|
||||
HideWindowOnClose: false,
|
||||
EnableDefaultContextMenu: true,
|
||||
BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255},
|
||||
Assets: assets,
|
||||
Menu: AppMenu,
|
||||
Logger: nil,
|
||||
LogLevel: logger.DEBUG,
|
||||
LogLevelProduction: logger.ERROR,
|
||||
OnStartup: app.startup,
|
||||
OnDomReady: app.domReady,
|
||||
OnBeforeClose: app.beforeClose,
|
||||
OnShutdown: app.shutdown,
|
||||
WindowStartState: options.Normal,
|
||||
Bind: []interface{}{
|
||||
app,
|
||||
},
|
||||
@@ -137,6 +147,15 @@ func main() {
|
||||
|
||||
}
|
||||
|
||||
func updateBasicInfo() {
|
||||
config := data.NewSettingsApi(&data.Settings{}).GetConfig()
|
||||
if config.UpdateBasicInfoOnStart {
|
||||
//更新基本信息
|
||||
go data.NewStockDataApi().GetStockBaseInfo()
|
||||
go data.NewStockDataApi().GetIndexBasic()
|
||||
}
|
||||
}
|
||||
|
||||
func initStockData() {
|
||||
var count int64
|
||||
db.Dao.Model(&data.StockBasic{}).Count(&count)
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
"frontend:dev:serverUrl": "http://localhost:5173",
|
||||
"author": {
|
||||
"name": "spark",
|
||||
"email": "wzl@huazx.cn"
|
||||
"email": "sparkmemory@163.com"
|
||||
},
|
||||
"info": {
|
||||
"companyName": "sparkmemory",
|
||||
"productName": "go-stock",
|
||||
"productVersion": "1.0.0",
|
||||
"copyright": "Copyright#sparkmemory@163.com",
|
||||
"comments": "股票行情实时获取"
|
||||
"comments": "股票行情实时获取,AI赋能分析股票"
|
||||
}
|
||||
}
|
||||
|
||||