Compare commits

..

19 Commits

Author SHA1 Message Date
spark
f35847823b feat(data): 添加指数信息获取功能
- 在 StockDataApi 中新增 GetIndexBasic 方法,用于获取指数信息
- 在数据库中添加 index_basic 表并进行自动迁移- 优化 GetStockBaseInfo 方法,使用 map 结构处理字段- 增加 GetIndexBasic 的单元测试
2025-01-02 17:52:25 +08:00
spark
15120c98da feat(frontend): 优化股票代码输入和搜索功能
- 改进股票列表显示格式,增加连字符分隔
- 添加支持直接输入股票代码进行搜索的功能
- 优化股票选择逻辑,支持不同格式的股票代码
-增加调试日志输出,便于问题排查
2025-01-02 15:39:39 +08:00
spark
f9a0c8d94e 样式修改,新增菜单工具栏 2024-12-28 14:41:45 +08:00
spark
0c04272153 样式修改,新增菜单工具栏 2024-12-25 13:13:23 +08:00
sparkmemory
cf7e8415e6 update 2024-12-23 11:26:24 +08:00
sparkmemory
1ab6875790 update 2024-12-23 11:25:33 +08:00
sparkmemory
cc40da8371 update 2024-12-23 11:21:03 +08:00
sparkmemory
29158f51af update 2024-12-23 07:34:58 +08:00
spark
9665462ae5 样式修改 2024-12-20 21:02:37 +08:00
spark
71e3953cd8 添加缩略图 2024-12-20 15:06:09 +08:00
spark
bedeaad1f2 添加按钮位置修改 2024-12-20 14:37:38 +08:00
spark
4f05820e2e 优化首次启动速度 2024-12-20 14:34:24 +08:00
spark
abf05aabd8 退出程序添加提示 2024-12-20 14:33:54 +08:00
spark
9016289e96 样式调整 2024-12-20 11:07:46 +08:00
spark
bab53711b7 添加全屏按钮 2024-12-20 11:01:56 +08:00
spark
de67f07f41 修改为深色主题 2024-12-20 10:01:56 +08:00
spark
869223cfb9 修改为深色主题 2024-12-20 10:00:46 +08:00
spark
d240239fcc 窗口标题显示当前时间 2024-12-19 20:59:01 +08:00
spark
6b62385cad 分时图和K线图展示 2024-12-19 13:48:02 +08:00
20 changed files with 319 additions and 64 deletions

1
.gitignore vendored
View File

@@ -109,3 +109,4 @@ dist
data/*.db
build/*.exe
/build/bin/go-stock-dev.exe
/build/bin/

View File

@@ -3,6 +3,16 @@
![Wails and NaiveUI](./build/appicon.png)
## Snapshot
![img_1.png](build/screenshot/img_1.png)
### 成本仓位设置
![img.png](build/screenshot/img.png)
### 日K
![img_2.png](build/screenshot/img_2.png)
### 分时
![img_3.png](build/screenshot/img_3.png)
## About
A China stock data viewer build by [Wails](https://wails.io/) with [NavieUI](https://www.naiveui.com/).

29
app.go
View File

@@ -2,7 +2,9 @@ package main
import (
"context"
"github.com/wailsapp/wails/v2/pkg/runtime"
"go-stock/backend/data"
"go-stock/backend/logger"
)
// App struct
@@ -22,14 +24,39 @@ func (a *App) startup(ctx context.Context) {
}
// domReady is called after front-end resources have been loaded
func (a App) domReady(ctx context.Context) {
func (a *App) domReady(ctx context.Context) {
// Add your action here
//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
}

View File

@@ -9,6 +9,7 @@ import (
"encoding/json"
"fmt"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/slice"
"github.com/duke-git/lancet/v2/strutil"
"github.com/go-resty/resty/v2"
"go-stock/backend/db"
@@ -16,6 +17,7 @@ import (
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"gorm.io/gorm"
"gorm.io/plugin/soft_delete"
"io/ioutil"
"strings"
"time"
@@ -134,6 +136,7 @@ type FollowedStock struct {
ChangePercent float64
Time time.Time
Sort int64
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
}
func (receiver FollowedStock) TableName() string {
@@ -160,21 +163,69 @@ func NewStockDataApi() *StockDataApi {
client: resty.New(),
}
}
// GetIndexBasic 获取指数信息
func (receiver StockDataApi) GetIndexBasic() {
res := &TushareStockBasicResponse{}
fields := "ts_code,name,market,publisher,category,base_date,base_point,list_date,fullname,index_type,weight_rule,desc"
resp, err := receiver.client.R().
SetHeader("content-type", "application/json").
SetBody(&TushareRequest{
ApiName: "index_basic",
Token: TushareToken,
Params: nil,
Fields: fields}).
SetResult(res).
Post(tushare_api_url)
if err != nil {
logger.SugaredLogger.Error(err.Error())
return
}
if res.Code != 0 {
logger.SugaredLogger.Error(res.Msg)
return
}
ioutil.WriteFile("index_basic.json", resp.Body(), 0666)
for _, item := range res.Data.Items {
data := map[string]any{}
for _, field := range strings.Split(fields, ",") {
logger.SugaredLogger.Infof("field: %s", field)
idx := slice.IndexOf(res.Data.Fields, field)
if idx == -1 {
continue
}
data[field] = item[idx]
}
index := &IndexBasic{}
jsonData, _ := json.Marshal(data)
err := json.Unmarshal(jsonData, index)
if err != nil {
continue
}
db.Dao.Model(&IndexBasic{}).FirstOrCreate(index, &IndexBasic{TsCode: index.TsCode}).Updates(index)
}
}
// map转换为结构体
func (receiver StockDataApi) GetStockBaseInfo() {
res := &TushareStockBasicResponse{}
_, err := receiver.client.R().
fields := "ts_code,symbol,name,area,industry,cnspell,market,list_date,act_name,act_ent_type,fullname,exchange,list_status,curr_type,enname,delist_date,is_hs"
resp, err := receiver.client.R().
SetHeader("content-type", "application/json").
SetBody(&TushareRequest{
ApiName: "stock_basic",
Token: TushareToken,
Params: nil,
Fields: "*",
Fields: fields,
}).
SetResult(res).
Post(tushare_api_url)
//logger.SugaredLogger.Infof("GetStockBaseInfo %s", string(resp.Body()))
//resp.Body()写入文件
//ioutil.WriteFile("stock_basic.json", resp.Body(), 0666)
ioutil.WriteFile("stock_basic.json", resp.Body(), 0666)
//logger.SugaredLogger.Infof("GetStockBaseInfo %+v", res)
if err != nil {
logger.SugaredLogger.Error(err.Error())
@@ -185,25 +236,21 @@ func (receiver StockDataApi) GetStockBaseInfo() {
return
}
for _, item := range res.Data.Items {
ID, _ := convertor.ToInt(item[6])
stock := &StockBasic{}
stock.Exchange = convertor.ToString(item[0])
stock.IsHs = convertor.ToString(item[1])
stock.Name = convertor.ToString(item[2])
stock.Industry = convertor.ToString(item[3])
stock.ListStatus = convertor.ToString(item[4])
stock.ActName = convertor.ToString(item[5])
stock.ID = uint(ID)
stock.CurrType = convertor.ToString(item[7])
stock.Area = convertor.ToString(item[8])
stock.ListDate = convertor.ToString(item[9])
stock.DelistDate = convertor.ToString(item[10])
stock.ActEntType = convertor.ToString(item[11])
stock.TsCode = convertor.ToString(item[12])
stock.Symbol = convertor.ToString(item[13])
stock.Cnspell = convertor.ToString(item[14])
stock.Fullname = convertor.ToString(item[20])
stock.Ename = convertor.ToString(item[21])
data := map[string]any{}
for _, field := range strings.Split(fields, ",") {
logger.SugaredLogger.Infof("field: %s", field)
idx := slice.IndexOf(res.Data.Fields, field)
if idx == -1 {
continue
}
data[field] = item[idx]
}
jsonData, _ := json.Marshal(data)
err := json.Unmarshal(jsonData, stock)
if err != nil {
continue
}
db.Dao.Model(&StockBasic{}).FirstOrCreate(stock, &StockBasic{TsCode: stock.TsCode}).Updates(stock)
}
@@ -231,6 +278,7 @@ func (receiver StockDataApi) GetStockCodeRealTimeData(StockCode string) (*StockI
}
func (receiver StockDataApi) Follow(stockCode string) string {
logger.SugaredLogger.Infof("Follow %s", stockCode)
stockInfo, err := receiver.GetStockCodeRealTimeData(stockCode)
if err != nil {
logger.SugaredLogger.Error(err.Error())
@@ -369,3 +417,24 @@ func ParseFullSingleStockData(data string) (*StockInfo, error) {
return stockInfo, nil
}
type IndexBasic struct {
gorm.Model
TsCode string `json:"ts_code" gorm:"index"`
Symbol string `json:"symbol" gorm:"index"`
Name string `json:"name" gorm:"index"`
FullName string `json:"fullname"`
IndexType string `json:"index_type"`
IndexCategory string `json:"category"`
Market string `json:"market"`
ListDate string `json:"list_date"`
BaseDate string `json:"base_date"`
BasePoint float64 `json:"base_point"`
Publisher string `json:"publisher"`
WeightRule string `json:"weight_rule"`
DESC string `json:"desc"`
}
func (IndexBasic) TableName() string {
return "tushare_index_basic"
}

View File

@@ -77,3 +77,9 @@ func TestFollowedList(t *testing.T) {
t.Log(stockDataApi.GetFollowList())
}
func TestStockDataApi_GetIndexBasic(t *testing.T) {
db.Init("../../data/stock.db")
stockDataApi := NewStockDataApi()
stockDataApi.GetIndexBasic()
}

Binary file not shown.

BIN
build/screenshot/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
build/screenshot/img_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
build/screenshot/img_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
build/screenshot/img_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

@@ -8,6 +8,7 @@
"name": "go-stock",
"version": "1.0.0",
"dependencies": {
"@vicons/ionicons5": "^0.13.0",
"vue": "^3.2.25"
},
"devDependencies": {
@@ -736,6 +737,11 @@
"@types/lodash": "*"
}
},
"node_modules/@vicons/ionicons5": {
"version": "0.13.0",
"resolved": "https://registry.npmmirror.com/@vicons/ionicons5/-/ionicons5-0.13.0.tgz",
"integrity": "sha512-zvZKBPjEXKN7AXNo2Na2uy+nvuv6SP4KAMQxpKL2vfHMj0fSvuw7JZcOPCjQC3e7ayssKnaoFVAhbYcW6v41qQ=="
},
"node_modules/@vitejs/plugin-vue": {
"version": "5.2.1",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz",

View File

@@ -9,6 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"@vicons/ionicons5": "^0.13.0",
"vue": "^3.2.25"
},
"devDependencies": {

View File

@@ -1 +1 @@
9ce62efac1fed08499bbf20c8a5fd1b2
bd20b6837e5729f2325cbbbaa79cbf1e

View File

@@ -1,11 +1,13 @@
<script setup>
import stockInfo from './components/stock.vue'
import {ref} from "vue";
import { darkTheme } from 'naive-ui'
const content = ref('数据来源于网络,仅供参考\n投资有风险,入市需谨慎')
</script>
<template>
<n-config-provider :theme="darkTheme">
<n-watermark
:content="content"
cross
@@ -27,6 +29,7 @@ const content = ref('数据来源于网络,仅供参考\n投资有风险,入市
</n-message-provider>
</n-flex>
</n-watermark>
</n-config-provider>
</template>
<style>

View File

@@ -1,7 +1,9 @@
<script setup>
import {h, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue'
import {onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue'
import {Greet, Follow, UnFollow, GetFollowList, GetStockList, SetCostPriceAndVolume} from '../../wailsjs/go/main/App'
import {NButton, NFlex, NForm, NFormItem, NInput, NInputNumber, NText, useMessage, useModal} from 'naive-ui'
import {NButton, NFlex, NForm, NFormItem, NInputNumber, NText, useMessage, useModal} from 'naive-ui'
import { WindowFullscreen,WindowUnfullscreen,EventsOn } from '../../wailsjs/runtime'
import {Add, StarOutline} from '@vicons/ionicons5'
const message = useMessage()
const modal = useModal()
@@ -13,6 +15,9 @@ const stockList=ref([])
const followList=ref([])
const options=ref([])
const modalShow = ref(false)
const modalShow2 = ref(false)
const modalShow3 = ref(false)
const addBTN = ref(true)
const formModel = ref({
name: "",
code: "",
@@ -23,7 +28,10 @@ const formModel = ref({
const data = reactive({
name: "",
code: "",
fenshiURL:"",
kURL:"",
resultText: "Please enter your name below 👇",
fullscreen: false,
})
@@ -56,8 +64,9 @@ onMounted(() => {
ticker.value=setInterval(() => {
if(isTradingTime()){
monitor()
data.fenshiURL='http://image.sinajs.cn/newchart/min/n/'+data.code+'.gif'+"?t="+Date.now()
}
}, 1000)
}, 3000)
})
@@ -66,6 +75,28 @@ onBeforeUnmount(() => {
clearInterval(ticker.value)
})
EventsOn("refresh",(data)=>{
message.success(data)
})
EventsOn("showSearch",(data)=>{
addBTN.value = data === 1;
})
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
})
})
//判断是否是A股交易时间
function isTradingTime() {
@@ -108,18 +139,22 @@ function removeMonitor(code,name) {
})
}
function getStockList(){
function getStockList(value){
console.log("getStockList",value)
let result;
result=stockList.value.filter(item => item.name.includes(data.name)||item.ts_code.includes(data.name))
options.value=result.map(item => {
return {
label: item.name+" "+item.ts_code,
label: item.name+" - "+item.ts_code,
value: item.ts_code
}
})
if(value.indexOf("-")<=0){
data.code=value
}
}
function monitor() {
async function monitor() {
for (let code of stocks.value) {
// console.log(code)
Greet(code).then(result => {
@@ -159,7 +194,15 @@ function monitor() {
}
}
function onSelect(item) {
data.code=item.split(".")[1].toLowerCase()+item.split(".")[0]
console.log("onSelect",item)
if(item.indexOf("-")>0){
item=item.split("-")[1].toLowerCase()
}
if(item.indexOf(".")>0){
data.code=item.split(".")[1].toLowerCase()+item.split(".")[0]
}
}
function search(code,name){
@@ -177,6 +220,20 @@ function setStock(code,name){
modalShow.value=true
}
function showFenshi(code,name){
data.code=code
data.name=name
data.fenshiURL='http://image.sinajs.cn/newchart/min/n/'+data.code+'.gif'+"?t="+Date.now()
modalShow2.value=true
}
function showK(code,name){
data.code=code
data.name=name
data.kURL='http://image.sinajs.cn/newchart/daily/n/'+data.code+'.gif'+"?t="+Date.now()
modalShow3.value=true
}
function updateCostPriceAndVolumeNew(code,price,volume){
console.log(code,price,volume)
SetCostPriceAndVolume(code,price,volume).then(result => {
@@ -194,56 +251,82 @@ function updateCostPriceAndVolumeNew(code,price,volume){
})
})
}
function fullscreen(){
if(data.fullscreen){
WindowUnfullscreen()
}else{
WindowFullscreen()
}
data.fullscreen=!data.fullscreen
}
</script>
<template>
<n-grid :x-gap="8" :cols="3" :y-gap="8">
<n-grid :x-gap="8" :cols="3" :y-gap="8" >
<n-gi v-for="result in results" >
<n-card size="medium" style="min-height: 270px" :data-code="result['股票代码']" :bordered="false" :title="result['股票名称']" :content-style="'font-size: 18px;'" closable @close="removeMonitor(result['股票代码'],result['股票名称'])">
<n-card :data-code="result['股票代码']" :bordered="false" :title="result['股票名称']" :closable="true" @close="removeMonitor(result['股票代码'],result['股票名称'])">
<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>&nbsp;
<n-text size="small" v-if="result.profitAmountToday>0" :type="result.type">{{result.profitAmountToday}}</n-text>
</n-gi>
</n-grid>
<n-grid :cols="2" :y-gap="4" :x-gap="4" :item-style="'font-size: 14px;'">
<n-gi>
<n-text :type="'info'">{{"最高 "+result["今日最高价"]+" "+result.highRate }}</n-text>
</n-gi>
<n-gi>
<n-text :type="'info'">{{"最低 "+result["今日最低价"]+" "+result.lowRate }}</n-text>
</n-gi>
<n-gi>
<n-text :type="'info'">{{"昨收 "+result["昨日收盘价"]}}</n-text>
</n-gi>
<n-gi>
<n-text :type="'info'">{{"今开 "+result["今日开盘价"]}}</n-text>
</n-gi>
</n-grid>
<n-grid :cols="2" :y-gap="4" :x-gap="4" >
<n-gi>
<n-text :type="'info'">{{"最高 "+result["今日最高价"]+" "+result.highRate }}</n-text>
</n-gi>
<n-gi>
<n-text :type="'info'">{{"最低 "+result["今日最低价"]+" "+result.lowRate }}</n-text>
</n-gi>
<n-gi>
<n-text :type="'info'">{{"昨收 "+result["昨日收盘价"]}}</n-text>
</n-gi>
<n-gi>
<n-text :type="'info'">{{"今开 "+result["今日开盘价"]}}</n-text>
</n-gi>
</n-grid>
<template #header-extra>
<n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{result.volume+""}}</n-tag>
<!-- <n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{result.volume+""}}</n-tag>-->
</template>
<template #footer>
<n-tag size="small" v-if="result.costPrice>0" :type="result.profitType">{{"成本:"+result.costPrice+" "+result.profit+"%"+" ( "+result.profitAmount+" ¥ )"}}</n-tag>
<n-flex justify="center">
<n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{result.volume+""}}</n-tag>
<n-tag size="small" v-if="result.costPrice>0" :type="result.profitType">{{"成本:"+result.costPrice+" "+result.profit+"%"+" ( "+result.profitAmount+" ¥ )"}}</n-tag>
</n-flex>
</template>
<template #action>
<n-flex justify="space-between">
<n-button size="tiny" type="info" @click="setStock(result['股票代码'],result['股票名称'])"> 设置 </n-button>
<n-button size="tiny" type="warning" @click="search(result['股票代码'],result['股票名称'])"> 详情 </n-button>
<n-text :type="'info'">{{result["日期"]+" "+result["时间"]}}</n-text>
<n-button size="tiny" type="info" @click="setStock(result['股票代码'],result['股票名称'])"> 成本 </n-button>
<n-button size="tiny" type="success" @click="showFenshi(result['股票代码'],result['股票名称'])"> 分时 </n-button>
<n-button size="tiny" type="error" @click="showK(result['股票代码'],result['股票名称'])"> 日K </n-button>
<n-button size="tiny" type="warning" @click="search(result['股票代码'],result['股票名称'])"> 详情 </n-button>
</n-flex>
</template>
</n-card >
</n-gi>
</n-grid>
<n-auto-complete v-model:value="data.name" type="text"
:input-props="{
autocomplete: 'disabled',
}"
:options="options"
placeholder="股票名称或者代码"
clearable class="input" @input="getStockList" :on-select="onSelect"/>
<n-button type="info" @click="AddStock"> 添加 </n-button>
<n-affix :trigger-bottom="60" v-if="addBTN">
<!-- <n-card :bordered="false">-->
<n-input-group>
<n-button type="info" @click="addBTN=false" >隐藏</n-button>
<n-auto-complete v-model:value="data.name"
:input-props="{
autocomplete: 'disabled',
}"
:options="options"
placeholder="请输入股票名称或者代码"
clearable @update-value="getStockList" :on-select="onSelect"/>
<n-button type="primary" @click="AddStock">
<n-icon :component="Add"/> &nbsp;关注该股票
</n-button>
</n-input-group>
<!-- </n-card>-->
</n-affix>
<n-modal transform-origin="center" size="small" v-model:show="modalShow" :title="formModel.name" style="width: 400px" :preset="'card'">
<n-form :model="formModel" :rules="{ costPrice: { required: true, message: '请输入成本'}, volume: { required: true, message: '请输入数量'} }" label-placement="left" label-width="80px">
<n-form-item label="成本(元)" path="costPrice">
@@ -257,6 +340,13 @@ function updateCostPriceAndVolumeNew(code,price,volume){
<n-button type="primary" @click="updateCostPriceAndVolumeNew(formModel.code,formModel.costPrice,formModel.volume)">保存</n-button>
</template>
</n-modal>
<n-modal v-model:show="modalShow2" :title="data.name" style="width: 600px" :preset="'card'">
<n-image :src="data.fenshiURL" />
</n-modal>
<n-modal v-model:show="modalShow3" :title="data.name" style="width: 600px" :preset="'card'">
<n-image :src="data.kURL" />
</n-modal>
</template>
<style scoped>

View File

@@ -11,6 +11,7 @@ export namespace data {
// Go type: time
Time: any;
Sort: number;
IsDel: number;
static createFrom(source: any = {}) {
return new FollowedStock(source);
@@ -27,6 +28,7 @@ export namespace data {
this.ChangePercent = source["ChangePercent"];
this.Time = this.convertValues(source["Time"], null);
this.Sort = source["Sort"];
this.IsDel = source["IsDel"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {

1
go.mod
View File

@@ -14,6 +14,7 @@ require (
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/gorm v1.25.12
gorm.io/plugin/dbresolver v1.5.3
gorm.io/plugin/soft_delete v1.2.1
)
require (

11
go.sum
View File

@@ -27,6 +27,8 @@ github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4P
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
@@ -53,6 +55,9 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -153,11 +158,17 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/sqlite v1.1.3 h1:BYfdVuZB5He/u9dt4qDpZqiqDJ6KhPqs5QUqsr/Eeuc=
gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c=
gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU=
gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE=
gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU=
gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=

38
main.go
View File

@@ -6,13 +6,17 @@ import (
"github.com/duke-git/lancet/v2/convertor"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/menu/keys"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/mac"
"github.com/wailsapp/wails/v2/pkg/options/windows"
"github.com/wailsapp/wails/v2/pkg/runtime"
"go-stock/backend/data"
"go-stock/backend/db"
"log"
"os"
"time"
)
//go:embed frontend/dist
@@ -31,16 +35,39 @@ func main() {
db.Dao.AutoMigrate(&data.StockInfo{})
db.Dao.AutoMigrate(&data.StockBasic{})
db.Dao.AutoMigrate(&data.FollowedStock{})
db.Dao.AutoMigrate(&data.IndexBasic{})
if stocksBin != nil && len(stocksBin) > 0 {
initStockData()
go initStockData()
}
data.NewStockDataApi().GetStockBaseInfo()
go data.NewStockDataApi().GetStockBaseInfo()
go data.NewStockDataApi().GetIndexBasic()
// Create an instance of the app structure
app := NewApp()
AppMenu := menu.NewMenu()
FileMenu := AppMenu.AddSubmenu("设置")
FileMenu.AddText("显示搜索框", keys.CmdOrCtrl("s"), func(callbackData *menu.CallbackData) {
runtime.EventsEmit(app.ctx, "showSearch", 1)
})
FileMenu.AddText("隐藏搜索框", keys.CmdOrCtrl("d"), func(callbackData *menu.CallbackData) {
runtime.EventsEmit(app.ctx, "showSearch", 0)
})
FileMenu.AddText("刷新数据", keys.CmdOrCtrl("r"), func(callbackData *menu.CallbackData) {
//runtime.EventsEmit(app.ctx, "refresh", "setting-"+time.Now().Format("2006-01-02 15:04:05"))
runtime.EventsEmit(app.ctx, "refreshFollowList", "refresh-"+time.Now().Format("2006-01-02 15:04:05"))
})
FileMenu.AddSeparator()
FileMenu.AddText("窗口全屏", keys.CmdOrCtrl("f"), func(callback *menu.CallbackData) {
runtime.WindowFullscreen(app.ctx)
callback.MenuItem.Hide()
})
FileMenu.AddText("窗口还原", keys.Key("Esc"), func(callback *menu.CallbackData) {
runtime.WindowUnfullscreen(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",
@@ -57,7 +84,7 @@ func main() {
HideWindowOnClose: false,
BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255},
Assets: assets,
Menu: nil,
Menu: AppMenu,
Logger: nil,
LogLevel: logger.DEBUG,
OnStartup: app.startup,
@@ -100,6 +127,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
}
func initStockData() {

View File

@@ -1 +1 @@
{"request_id":"359a096c-0a5a-4b07-818f-3241738d5e73","code":40203,"data":null,"msg":"抱歉您每小时最多访问该接口1次权限的具体详情访问https://tushare.pro/document/1?doc_id=108。"}
{"request_id":"114e84e2-6d34-4379-8a30-4793e9df765f","code":40203,"data":null,"msg":"抱歉您每小时最多访问该接口1次权限的具体详情访问https://tushare.pro/document/1?doc_id=108。"}