feat(frontend): 优化用户界面和功能

- 添加全屏切换功能
- 实现窗口隐藏和退出功能
- 新增设置菜单
- 优化股票信息展示界面
- 调整窗口大小和布局
This commit is contained in:
spark 2025-01-10 15:32:22 +08:00
parent 88fb3ce94c
commit b1a0e9575b
9 changed files with 191 additions and 53 deletions

43
app.go
View File

@ -177,6 +177,11 @@ func addStockFollowData(follow data.FollowedStock, stockData *data.StockInfo) {
//昨日收盘价
preClosePrice, _ := convertor.ToFloat(stockData.PreClose)
//当前价格依然为0 时 使用昨日收盘价为当前价格
if price == 0 {
price = preClosePrice
}
//今日最高价
highPrice, _ := convertor.ToFloat(stockData.High)
if highPrice == 0 {
@ -191,20 +196,34 @@ func addStockFollowData(follow data.FollowedStock, stockData *data.StockInfo) {
//开盘价
//openPrice, _ := convertor.ToFloat(stockData.Open)
stockData.ChangePrice = mathutil.RoundToFloat(price-preClosePrice, 2)
stockData.ChangePercent = mathutil.RoundToFloat(mathutil.Div(price-preClosePrice, preClosePrice)*100, 3)
stockData.HighRate = mathutil.RoundToFloat(mathutil.Div(highPrice-preClosePrice, preClosePrice)*100, 3)
stockData.LowRate = mathutil.RoundToFloat(mathutil.Div(lowPrice-preClosePrice, preClosePrice)*100, 3)
if 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 {
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)
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 {
if follow.Price != price && price > 0 {
go db.Dao.Model(follow).Where("stock_code = ?", follow.StockCode).Updates(map[string]interface{}{
"price": stockData.Price,
"price": price,
})
}
}
@ -306,10 +325,8 @@ func (a *App) SendDingDingMessageByType(message string, stockCode string, msgTyp
}
stockInfo := &data.StockInfo{}
db.Dao.Model(stockInfo).Where("code = ?", stockCode).First(stockInfo)
if !data.NewAlertWindowsApi("go-stock消息通知", getMsgTypeName(msgType), GenNotificationMsg(stockInfo), "").SendNotification() {
return data.NewDingDingAPI().SendDingDingMessage(message)
}
return "发送系统消息成功"
go data.NewAlertWindowsApi("go-stock消息通知", getMsgTypeName(msgType), GenNotificationMsg(stockInfo), "").SendNotification()
return data.NewDingDingAPI().SendDingDingMessage(message)
}
func GenNotificationMsg(stockInfo *data.StockInfo) string {

View File

@ -295,14 +295,19 @@ func (receiver StockDataApi) GetStockCodeRealTimeData(StockCodes ...string) (*[]
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)
}
}()
}
//var count int64
//db.Dao.Model(&StockInfo{}).Where("code = ?", StockCode).Count(&count)
//if count == 0 {
// go db.Dao.Model(&StockInfo{}).Create(stockData)
//} else {
// go db.Dao.Model(&StockInfo{}).Where("code = ?", StockCode).Updates(stockData)
//}
return &stockInfos, err
}

View File

@ -36,6 +36,7 @@ func TestParseFullSingleStockData(t *testing.T) {
}
func TestNewStockDataApi(t *testing.T) {
db.Init("../../data/stock.db")
stockDataApi := NewStockDataApi()
datas, _ := stockDataApi.GetStockCodeRealTimeData("sh600859", "sh600745")
for _, data := range *datas {

View File

@ -9,7 +9,8 @@
"version": "1.0.0",
"dependencies": {
"@vicons/ionicons5": "^0.13.0",
"vue": "^3.2.25"
"vue": "^3.2.25",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
@ -801,6 +802,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",
@ -1241,6 +1247,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",

View File

@ -10,7 +10,8 @@
},
"dependencies": {
"@vicons/ionicons5": "^0.13.0",
"vue": "^3.2.25"
"vue": "^3.2.25",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",

View File

@ -1 +1 @@
fda5308055dfd8a9cbfbd89ea27276c4
00b8e620dca5bab58d37996f0f350d0a

View File

@ -1,13 +1,111 @@
<script setup>
import stockInfo from './components/stock.vue'
import {ref} from "vue";
import { darkTheme } from 'naive-ui'
import {
Quit,
WindowFullscreen,
WindowHide,
WindowIsFullscreen,
WindowUnfullscreen
} from '../wailsjs/runtime'
import {h, ref} from "vue";
import {darkTheme, NIcon} from 'naive-ui'
import {
SettingsOutline,
ReorderTwoOutline,
ExpandOutline,
RefreshOutline, PowerOutline,
} from '@vicons/ionicons5'
const content = ref('数据来源于网络,仅供参考\n投资有风险,入市需谨慎')
</script>
const isFullscreen = ref(false)
const activeKey = ref('stock')
const containerRef= ref({})
const menuOptions = ref([
{
label: '设置',
key: 'settings',
icon: renderIcon(SettingsOutline),
children: [
{
type: 'group',
label: '开发中',
key: 'setting',
children: [
// {
// label: '',
// key: 'narrator',
// icon: renderIcon(PersonIcon)
// },
// {
// label: '',
// key: 'sheep-man',
// icon: renderIcon(PersonIcon)
// }
]
},
]
},
{
label: ()=> h("a", {
href: '#',
onClick: toggleFullscreen
}, { default: () => '全屏' }),
key: 'full',
icon: renderIcon(ExpandOutline),
},
{
label: ()=> h("a", {
href: '#',
onClick: WindowHide,
}, { default: () => '隐藏到托盘区' }),
key: 'hide',
icon: renderIcon(ReorderTwoOutline),
},
{
label: () =>
h(
'a',
{
href: '/',
target: '_self'
},
{ default: () => '刷新' }
),
key: 'stock',
icon: renderIcon(RefreshOutline)
},
{
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)
WindowIsFullscreen().then((isFull) => {
isFullscreen.value = isFull
if (isFull) {
WindowUnfullscreen()
e.target.innerHTML = '全屏'
} else {
WindowFullscreen()
e.target.innerHTML = '取消全屏'
}
})
}
</script>
<template>
<n-config-provider :theme="darkTheme">
<n-config-provider :theme="darkTheme" ref="containerRef">
<n-watermark
:content="content"
cross
@ -21,10 +119,26 @@ const content = ref('数据来源于网络,仅供参考\n投资有风险,入市
:rotate="-15"
style="height: 100%"
>
<n-flex justify="center">
<n-message-provider >
<n-modal-provider>
<stockInfo/>
<n-grid x-gap="12" :cols="1">
<n-gi>
<stockInfo/>
</n-gi>
<n-gi style="position: sticky;bottom:0;z-index: 9999;">
<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-modal-provider>
</n-message-provider>
</n-flex>

View File

@ -334,13 +334,13 @@ function SendMessage(result,type){
let img='http://image.sinajs.cn/newchart/min/n/'+result["股票代码"]+'.gif'+"?t="+Date.now()
let markdown="### go-stock ["+typeName+"]\n\n"+
"### "+result["股票名称"]+"("+result["股票代码"]+")\n" +
"- 当前价格: "+result["当前价格"]+" "+result.s+"\n" +
"- 当前价格: "+result["当前价格"]+" "+result.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"+
"![image]("+img+")\n"
let title=result["股票名称"]+"("+result["股票代码"]+") "+result["当前价格"]+" "+result.s
@ -432,7 +432,7 @@ function getHeight() {
</n-card >
</n-gi>
</n-grid>
<n-affix :trigger-bottom="getHeight()/2-10" style="left:0">
<n-affix :trigger-bottom="15" style="right:0;z-index: 99990;">
<!-- <n-card :bordered="false">-->
<n-input-group>
@ -506,25 +506,5 @@ function getHeight() {
</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>

View File

@ -82,14 +82,14 @@ func main() {
err := wails.Run(&options.App{
Title: "go-stock",
Width: 1366,
Height: 860,
Height: 920,
MinWidth: 1024,
MinHeight: 768,
MaxWidth: 1280,
MaxHeight: 960,
DisableResize: false,
Fullscreen: false,
Frameless: false,
Frameless: true,
StartHidden: false,
HideWindowOnClose: false,
BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255},