feat(frontend):添加股票资金趋势功能

- 在前端添加了股票资金趋势页面组件
- 在后端实现了获取股票资金趋势数据的接口
- 优化了前端界面布局,增加了资金趋势按钮
This commit is contained in:
ArvinLovegood 2025-05-15 18:36:53 +08:00
parent 4fd5cbf8e6
commit 2a274db7ae
8 changed files with 332 additions and 1 deletions

5
app.go
View File

@ -1131,3 +1131,8 @@ func (a *App) GetMoneyRankSina(sort string) []map[string]any {
res := data.NewMarketNewsApi().GetMoneyRankSina(sort) res := data.NewMarketNewsApi().GetMoneyRankSina(sort)
return res return res
} }
func (a *App) GetStockMoneyTrendByDay(stockCode string, days int) []map[string]any {
res := data.NewMarketNewsApi().GetStockMoneyTrendByDay(stockCode, days)
return res
}

View File

@ -275,3 +275,21 @@ func (m MarketNewsApi) GetMoneyRankSina(sort string) []map[string]any {
} }
return *res return *res
} }
func (m MarketNewsApi) GetStockMoneyTrendByDay(stockCode string, days int) []map[string]any {
url := fmt.Sprintf("http://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/MoneyFlow.ssl_qsfx_zjlrqs?page=1&num=%d&sort=opendate&asc=0&daima=%s", days, stockCode)
response, _ := resty.New().SetTimeout(time.Duration(5)*time.Second).R().
SetHeader("Host", "vip.stock.finance.sina.com.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/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").Get(url)
js := string(response.Body())
res := &[]map[string]any{}
err := json.Unmarshal([]byte(js), &res)
if err != nil {
logger.SugaredLogger.Error(err)
return *res
}
return *res
}

View File

@ -49,3 +49,10 @@ func TestGetMoneyRankSina(t *testing.T) {
} }
} }
func TestGetStockMoneyTrendByDay(t *testing.T) {
res := NewMarketNewsApi().GetStockMoneyTrendByDay("sh600438", 120)
for i, re := range res {
logger.SugaredLogger.Debugf("key: %+v, value: %+v", i, re)
}
}

View File

@ -446,7 +446,7 @@ onBeforeMount(() => {
}) })
onMounted(() => { onMounted(() => {
contentStyle.value = "max-height: calc(90vh);overflow: hidden" contentStyle.value = "max-height: calc(92vh);overflow: hidden"
GetConfig().then((res) => { GetConfig().then((res) => {
if (res.enableNews) { if (res.enableNews) {
enableNews.value = true enableNews.value = true
@ -488,6 +488,7 @@ onMounted(() => {
</n-tag> </n-tag>
</n-marquee> </n-marquee>
<n-scrollbar :style="contentStyle"> <n-scrollbar :style="contentStyle">
<n-skeleton v-if="loading" height="calc(100vh)" />
<RouterView/> <RouterView/>
</n-scrollbar> </n-scrollbar>
</n-spin> </n-spin>

View File

@ -0,0 +1,279 @@
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {GetStockMoneyTrendByDay} from "../../wailsjs/go/main/App";
import * as echarts from "echarts";
const {code, name, darkTheme, days, chartHeight} = defineProps({
code: {
type: String,
default: ''
},
name: {
type: String,
default: ''
},
days: {
type: Number,
default: 14
},
chartHeight: {
type: Number,
default: 500
},
darkTheme: {
type: Boolean,
default: false
}
})
const LineChartRef = ref(null);
onMounted(
() => {
handleLine(code, days)
}
)
const handleLine = (code, days) => {
GetStockMoneyTrendByDay(code, days).then(result => {
console.log("GetStockMoneyTrendByDay", result)
const chart = echarts.init(LineChartRef.value);
const categoryData = [];
const netamount_values = [];
const trades_values = [];
let volume = []
let min = 0
let max = 0
for (let i = 0; i < result.length; i++) {
let resultElement = result[i]
categoryData.push(resultElement.opendate)
let netamount = (resultElement.netamount / 10000).toFixed(2);
netamount_values.push(netamount)
let price = Number(resultElement.trade);
trades_values.push(price)
if (min === 0 || min > price) {
min = price
}
if (max < price) {
max = price
}
if (i > 0) {
let b=(result[i].netamount - result[i - 1].netamount)/10000
volume.push(b)
} else {
volume.push(result[i].netamount/10000)
}
}
console.log("trades_values", trades_values)
let option = {
darkMode: darkTheme,
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
animation: false,
label: {
backgroundColor: '#505765'
}
}
},
axisPointer: {
link: [
{
xAxisIndex: 'all'
}
],
label: {
backgroundColor: '#888'
}
},
legend: {
show: true,
data: ['当日净流入','累计净流入', '股价'],
selected: {
'当日净流入': true,
'累计净流入': false,
'股价': true,
},
//orient: 'vertical',
textStyle: {
color: darkTheme ? '#ccc' : '#456'
},
right: 150,
},
dataZoom: [
{
show: true,
realtime: true,
start: 70,
end: 100
},
{
type: 'inside',
realtime: true,
start: 86,
end: 100
}
],
xAxis: {
type: 'category',
data: categoryData
},
yAxis: [
{
name: '净流入',
type: 'value',
axisLine: {
show: true
},
splitLine: {
show: false
},
},
{
name: '股价',
type: 'value',
min: min - 1,
max: max + 1,
minInterval: 0.01,
axisLine: {
show: true
},
splitLine: {
show: false
},
}
],
series: [
{
name: '当日净流入',
data: netamount_values,
smooth: true,
showSymbol: false,
lineStyle: {
width: 2
},
markPoint: {
symbol: 'arrow',
symbolRotate: 90,
symbolSize: [10, 20],
symbolOffset: [10, 0],
itemStyle: {
color: '#FC290D'
},
label: {
position: 'right',
},
data: [
{type: 'max', name: 'Max'},
{type: 'min', name: 'Min'}
]
},
markLine: {
data: [
{
type: 'average',
name: 'Average',
lineStyle: {
color: '#ff001e',
width: 0.5
},
},
]
},
type: 'line'
},
{
name: '累计净流入',
data: volume,
smooth: true,
showSymbol: false,
lineStyle: {
width: 2
},
markPoint: {
symbol: 'arrow',
symbolRotate: 90,
symbolSize: [10, 20],
symbolOffset: [10, 0],
itemStyle: {
color: '#FC290D'
},
label: {
position: 'right',
},
data: [
{type: 'max', name: 'Max'},
{type: 'min', name: 'Min'}
]
},
markLine: {
data: [
{
type: 'average',
name: 'Average',
lineStyle: {
color: '#ff001e',
width: 0.5
},
},
]
},
type: 'line'
},
{
name: '股价',
type: 'line',
data: trades_values,
yAxisIndex: 1,
smooth: true,
showSymbol: false,
lineStyle: {
width: 3
},
markPoint: {
symbol: 'arrow',
symbolRotate: 90,
symbolSize: [10, 20],
symbolOffset: [10, 0],
itemStyle: {
color: '#0940f3'
},
label: {
position: 'right',
},
data: [
{type: 'max', name: 'Max'},
{type: 'min', name: 'Min'}
]
},
markLine: {
data: [
{
type: 'average',
name: 'Average',
lineStyle: {
color: '#0940f3',
width: 0.5
},
},
]
},
}
]
};
chart.setOption(option);
})
}
</script>
<template>
<div ref="LineChartRef" style="width: 100%;height: auto;" :style="{height:chartHeight+'px'}"></div>
</template>
<style scoped>
</style>

View File

@ -63,6 +63,7 @@ import {asBlob} from 'html-docx-js-typescript';
import vueDanmaku from 'vue3-danmaku' import vueDanmaku from 'vue3-danmaku'
import {keys, pad, padStart} from "lodash"; import {keys, pad, padStart} from "lodash";
import {useRoute, useRouter} from 'vue-router' import {useRoute, useRouter} from 'vue-router'
import MoneyTrend from "./moneyTrend.vue";
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -98,6 +99,7 @@ const modalShow = ref(false)
const modalShow2 = ref(false) const modalShow2 = ref(false)
const modalShow3 = ref(false) const modalShow3 = ref(false)
const modalShow4 = ref(false) const modalShow4 = ref(false)
const modalShow5 = ref(false)
const addBTN = ref(true) const addBTN = ref(true)
const formModel = ref({ const formModel = ref({
name: "", name: "",
@ -1241,6 +1243,12 @@ function handleKLine(){
}); });
}) })
} }
function showMoney(code,name){
data.code=code
data.name=name
modalShow5.value=true
}
function showK(code,name){ function showK(code,name){
data.code=code data.code=code
data.name=name data.name=name
@ -1710,6 +1718,8 @@ function delStockGroup(code,name,groupId){
<n-button size="tiny" type="info" @click="setStock(result['股票代码'],result['股票名称'])"> 成本 </n-button> <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="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="error" @click="showK(result['股票代码'],result['股票名称'])"> 日K </n-button>
<n-button size="tiny" type="error" v-if="result['买一报价']>0" @click="showMoney(result['股票代码'],result['股票名称'])"> 资金 </n-button>
<n-button size="tiny" type="warning" @click="search(result['股票代码'],result['股票名称'])"> 详情 </n-button> <n-button size="tiny" type="warning" @click="search(result['股票代码'],result['股票名称'])"> 详情 </n-button>
<n-dropdown trigger="click" :options="groupList" key-field="ID" label-field="name" @select="(groupId) => AddStockGroupInfo(groupId,result['股票代码'],result['股票名称'])"> <n-dropdown trigger="click" :options="groupList" key-field="ID" label-field="name" @select="(groupId) => AddStockGroupInfo(groupId,result['股票代码'],result['股票名称'])">
<n-button type="success" size="tiny">设置分组</n-button> <n-button type="success" size="tiny">设置分组</n-button>
@ -1821,6 +1831,8 @@ function delStockGroup(code,name,groupId){
<n-button size="tiny" type="info" @click="setStock(result['股票代码'],result['股票名称'])"> 成本 </n-button> <n-button size="tiny" type="info" @click="setStock(result['股票代码'],result['股票名称'])"> 成本 </n-button>
<n-button size="tiny" type="success" @click="showFenshi(result['股票代码'],result['股票名称'],result.changePercent)"> 分时 </n-button> <n-button size="tiny" type="success" @click="showFenshi(result['股票代码'],result['股票名称'],result.changePercent)"> 分时 </n-button>
<n-button size="tiny" type="error" @click="showK(result['股票代码'],result['股票名称'])"> 日K </n-button> <n-button size="tiny" type="error" @click="showK(result['股票代码'],result['股票名称'])"> 日K </n-button>
<n-button size="tiny" type="error" v-if="result['买一报价']>0" @click="showMoney(result['股票代码'],result['股票名称'])"> 资金 </n-button>
<n-button size="tiny" type="warning" @click="search(result['股票代码'],result['股票名称'])"> 详情 </n-button> <n-button size="tiny" type="warning" @click="search(result['股票代码'],result['股票名称'])"> 详情 </n-button>
<n-dropdown trigger="click" :options="groupList" key-field="ID" label-field="name" @select="(groupId) => AddStockGroupInfo(groupId,result['股票代码'],result['股票名称'])"> <n-dropdown trigger="click" :options="groupList" key-field="ID" label-field="name" @select="(groupId) => AddStockGroupInfo(groupId,result['股票代码'],result['股票名称'])">
<n-button type="success" size="tiny">设置分组</n-button> <n-button type="success" size="tiny">设置分组</n-button>
@ -1988,6 +2000,9 @@ function delStockGroup(code,name,groupId){
</n-flex> </n-flex>
</template> </template>
</n-modal> </n-modal>
<n-modal v-model:show="modalShow5" :title="data.name+'资金趋势'" style="width: 1000px" :preset="'card'" @after-enter="getMoneyTrendLine">
<money-trend :code="data.code" :name="data.name" :days="365" :dark-theme="data.darkTheme" :chart-height="500"></money-trend>
</n-modal>
</template> </template>
<style scoped> <style scoped>

View File

@ -49,6 +49,8 @@ export function GetStockList(arg1:string):Promise<Array<data.StockBasic>>;
export function GetStockMinutePriceLineData(arg1:string,arg2:string):Promise<Record<string, any>>; export function GetStockMinutePriceLineData(arg1:string,arg2:string):Promise<Record<string, any>>;
export function GetStockMoneyTrendByDay(arg1:string,arg2:number):Promise<Array<Record<string, any>>>;
export function GetTelegraphList(arg1:string):Promise<any>; export function GetTelegraphList(arg1:string):Promise<any>;
export function GetVersionInfo():Promise<models.VersionInfo>; export function GetVersionInfo():Promise<models.VersionInfo>;

View File

@ -94,6 +94,10 @@ export function GetStockMinutePriceLineData(arg1, arg2) {
return window['go']['main']['App']['GetStockMinutePriceLineData'](arg1, arg2); return window['go']['main']['App']['GetStockMinutePriceLineData'](arg1, arg2);
} }
export function GetStockMoneyTrendByDay(arg1, arg2) {
return window['go']['main']['App']['GetStockMoneyTrendByDay'](arg1, arg2);
}
export function GetTelegraphList(arg1) { export function GetTelegraphList(arg1) {
return window['go']['main']['App']['GetTelegraphList'](arg1); return window['go']['main']['App']['GetTelegraphList'](arg1);
} }