Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f35847823b | ||
|
|
15120c98da | ||
|
|
f9a0c8d94e | ||
|
|
0c04272153 | ||
|
|
cf7e8415e6 | ||
|
|
1ab6875790 | ||
|
|
cc40da8371 | ||
|
|
29158f51af | ||
|
|
9665462ae5 | ||
|
|
71e3953cd8 | ||
|
|
bedeaad1f2 | ||
|
|
4f05820e2e | ||
|
|
abf05aabd8 | ||
|
|
9016289e96 | ||
|
|
bab53711b7 | ||
|
|
de67f07f41 | ||
|
|
869223cfb9 | ||
|
|
d240239fcc | ||
|
|
6b62385cad |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -109,3 +109,4 @@ dist
|
||||
data/*.db
|
||||
build/*.exe
|
||||
/build/bin/go-stock-dev.exe
|
||||
/build/bin/
|
||||
|
||||
10
README.md
10
README.md
@@ -3,6 +3,16 @@
|
||||

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

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

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

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

|
||||
|
||||
|
||||
## About
|
||||
|
||||
A China stock data viewer build by [Wails](https://wails.io/) with [NavieUI](https://www.naiveui.com/).
|
||||
|
||||
29
app.go
29
app.go
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
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
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
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
BIN
build/screenshot/img_3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
6
frontend/package-lock.json
generated
6
frontend/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"vue": "^3.2.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1 +1 @@
|
||||
9ce62efac1fed08499bbf20c8a5fd1b2
|
||||
bd20b6837e5729f2325cbbbaa79cbf1e
|
||||
@@ -1,11 +1,13 @@
|
||||
<script setup>
|
||||
import stockInfo from './components/stock.vue'
|
||||
import {ref} from "vue";
|
||||
import { darkTheme } from 'naive-ui'
|
||||
|
||||
const content = ref('数据来源于网络,仅供参考\n投资有风险,入市需谨慎')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-config-provider :theme="darkTheme">
|
||||
<n-watermark
|
||||
:content="content"
|
||||
cross
|
||||
@@ -27,6 +29,7 @@ const content = ref('数据来源于网络,仅供参考\n投资有风险,入市
|
||||
</n-message-provider>
|
||||
</n-flex>
|
||||
</n-watermark>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
<style>
|
||||
|
||||
|
||||
@@ -1,7 +1,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>
|
||||
<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"/> 关注该股票
|
||||
</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>
|
||||
|
||||
@@ -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
1
go.mod
@@ -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
11
go.sum
@@ -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
38
main.go
@@ -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() {
|
||||
|
||||
@@ -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。"}
|
||||
Reference in New Issue
Block a user