feat(frontend):优化股票K线图功能

- 在 App.d.ts 中添加 GetStockKLine 函数声明
- 在 app.go 中实现 GetStockKLine 方法- 在 App.js 中添加 GetStockKLine 函数导出
- 在 package.json 中添加 echarts依赖
- 在 stock.vue 中实现 K 线图展示功能- 优化 K 线图数据处理和图表配置
This commit is contained in:
ArvinLovegood 2025-04-22 11:43:31 +08:00
parent db3594af77
commit 71d8822d15
9 changed files with 364 additions and 8 deletions

4
app.go
View File

@ -1043,3 +1043,7 @@ func (a *App) RemoveGroup(groupId int) string {
return "移除失败"
}
}
func (a *App) GetStockKLine(stockCode, stockName string, days int64) *[]data.KLineData {
return data.NewStockDataApi().GetHK_KLineData(stockCode, "day", days)
}

View File

@ -1124,7 +1124,7 @@ func (receiver StockDataApi) GetHK_KLineData(stockCode string, kLineType string,
logger.SugaredLogger.Errorf("err:%s", err.Error())
return K
}
//logger.SugaredLogger.Infof("resp:%s", resp.Body())
logger.SugaredLogger.Infof("resp:%s", resp.Body())
json.Unmarshal(resp.Body(), &res)
code, _ := convertor.ToInt(res["code"])
if code != 0 {

View File

@ -77,7 +77,7 @@ func TestGetKLineData(t *testing.T) {
}
func TestGetHK_KLineData(t *testing.T) {
db.Init("../../data/stock.db")
k := NewStockDataApi().GetHK_KLineData("sh600171", "day", 30)
k := NewStockDataApi().GetHK_KLineData("hk01810", "day", 1)
jsonData, _ := json.Marshal(*k)
markdownTable, err := JSONToMarkdownTable(jsonData)
if err != nil {

View File

@ -12,6 +12,7 @@
"@vavt/cm-extension": "^1.8.0",
"@vavt/v3-extension": "^3.0.0",
"@vicons/ionicons5": "^0.13.0",
"echarts": "^5.6.0",
"file-saver": "^2.0.5",
"html2canvas": "^1.4.1",
"lodash": "^4.17.21",
@ -1593,6 +1594,20 @@
"date-fns": "^3.0.0 || ^4.0.0"
}
},
"node_modules/echarts": {
"version": "5.6.0",
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz",
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.6.1"
}
},
"node_modules/echarts/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
@ -2314,6 +2329,19 @@
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/zrender": {
"version": "5.6.1",
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
"dependencies": {
"tslib": "2.3.0"
}
},
"node_modules/zrender/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
}
}
}

View File

@ -13,6 +13,7 @@
"@vavt/cm-extension": "^1.8.0",
"@vavt/v3-extension": "^3.0.0",
"@vicons/ionicons5": "^0.13.0",
"echarts": "^5.6.0",
"file-saver": "^2.0.5",
"html2canvas": "^1.4.1",
"lodash": "^4.17.21",

View File

@ -1 +1 @@
cf858d682535e094e087a036e009d7f8
99aeae4d0e7cbe900b379d3e7d2f44d7

View File

@ -1,5 +1,6 @@
<script setup>
import {computed, h, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue'
import * as echarts from 'echarts';
import {
Follow,
GetAIResponseResult,
@ -22,7 +23,7 @@ import {
AddGroup,
GetGroupList,
AddStockGroup,
RemoveStockGroup, RemoveGroup
RemoveStockGroup, RemoveGroup,GetStockKLine
} from '../../wailsjs/go/main/App'
import {
NAvatar,
@ -59,6 +60,14 @@ const danmus = ref([])
const ws = ref(null)
const dialog = useDialog()
const toolbars = [0];
const upColor = '#ec0000';
const upBorderColor = '';
const downColor = '#00da3c';
const downBorderColor = '';
const kLineChartRef = ref(null);
const handleProgress = (progress) => {
console.log(`Export progress: ${progress.ratio * 100}%`);
};
@ -522,7 +531,7 @@ async function updateData(result) {
delete results.value[result.key]
}
console.log("updateData",result)
//console.log("updateData",result)
}
@ -592,6 +601,313 @@ function showFenshi(code,name){
modalShow2.value=true
}
function calculateMA(dayCount,values) {
var result = [];
for (var i = 0, len = values.length; i < len; i++) {
if (i < dayCount) {
result.push('-');
continue;
}
var sum = 0;
for (var j = 0; j < dayCount; j++) {
sum += +values[i - j][1];
}
result.push((sum / dayCount).toFixed(2));
}
return result;
}
function handleKLine(){
GetStockKLine(data.code,data.name,365).then(result => {
console.log("GetStockKLine",result)
const chart = echarts.init(kLineChartRef.value);
const categoryData = [];
const values = [];
const volumns=[];
for (let i = 0; i < result.length; i++) {
let resultElement=result[i]
console.log("resultElement:{}",resultElement)
categoryData.push(resultElement.day)
let flag=resultElement.close>resultElement.open?1:-1
values.push([
resultElement.open,
resultElement.close,
resultElement.low,
resultElement.high
])
volumns.push([i,resultElement.volume/100,flag])
}
//console.log("categoryData",categoryData)
//console.log("values",values)
let option = {
backgroundColor: '#ffffff',
color:['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'],
animation: false,
legend: {
bottom: 10,
left: 'center',
data: ['日K', 'MA5', 'MA10', 'MA20', 'MA30']
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
borderWidth: 1,
borderColor: '#ccc',
padding: 10,
textStyle: {
color: '#000'
},
position: function (pos, params, el, elRect, size) {
const obj = {
top: 10
};
obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 30;
return obj;
}
// extraCssText: 'width: 170px'
},
axisPointer: {
link: [
{
xAxisIndex: 'all'
}
],
label: {
backgroundColor: '#777'
}
},
visualMap: {
show: false,
seriesIndex: 5,
dimension: 2,
pieces: [
{
value: 1,
color: downColor
},
{
value: -1,
color: upColor
}
]
},
grid: [
{
left: '10%',
right: '8%',
height: '50%'
},
{
left: '10%',
right: '8%',
top: '63%',
height: '16%'
}
],
xAxis: [
{
type: 'category',
data: categoryData,
boundaryGap: false,
axisLine: { onZero: false },
splitLine: { show: false },
min: 'dataMin',
max: 'dataMax',
axisPointer: {
z: 100
}
},
{
type: 'category',
gridIndex: 1,
data: categoryData,
boundaryGap: false,
axisLine: { onZero: false },
axisTick: { show: false },
splitLine: { show: false },
axisLabel: { show: false },
min: 'dataMin',
max: 'dataMax'
}
],
yAxis: [
{
scale: true,
splitArea: {
show: true
}
},
{
scale: true,
gridIndex: 1,
splitNumber: 2,
axisLabel: { show: false },
axisLine: { show: false },
axisTick: { show: false },
splitLine: { show: false }
}
],
dataZoom: [
{
type: 'inside',
xAxisIndex: [0, 1],
start: 50,
end: 100
},
{
show: true,
xAxisIndex: [0, 1],
type: 'slider',
top: '85%',
start: 50,
end: 100
}
],
series: [
{
name: '日K',
type: 'candlestick',
data: values,
itemStyle: {
color: upColor,
color0: downColor,
// borderColor: upBorderColor,
// borderColor0: downBorderColor
},
markPoint: {
label: {
formatter: function (param) {
return param != null ? param.value + '' : '';
}
},
data: [
{
name: '最高',
type: 'max',
valueDim: 'highest'
},
{
name: '最低',
type: 'min',
valueDim: 'lowest'
},
{
name: '平均收盘价',
type: 'average',
valueDim: 'close'
}
],
tooltip: {
formatter: function (param) {
return param.name + '<br>' + (param.data.coord || '');
}
}
},
markLine: {
symbol: ['none', 'none'],
data: [
[
{
name: 'from lowest to highest',
type: 'min',
valueDim: 'lowest',
symbol: 'circle',
symbolSize: 10,
label: {
show: false
},
emphasis: {
label: {
show: false
}
}
},
{
type: 'max',
valueDim: 'highest',
symbol: 'circle',
symbolSize: 10,
label: {
show: false
},
emphasis: {
label: {
show: false
}
}
}
],
{
name: 'min line on close',
type: 'min',
valueDim: 'close'
},
{
name: 'max line on close',
type: 'max',
valueDim: 'close'
}
]
}
},
{
name: 'MA5',
type: 'line',
data: calculateMA(5,values),
smooth: true,
showSymbol: false,
lineStyle: {
opacity: 0.5
}
},
{
name: 'MA10',
type: 'line',
data: calculateMA(10,values),
smooth: true,
showSymbol: false,
lineStyle: {
opacity: 0.5
}
},
{
name: 'MA20',
type: 'line',
data: calculateMA(20,values),
smooth: true,
showSymbol: false,
lineStyle: {
opacity: 0.5
}
},
{
name: 'MA30',
type: 'line',
data: calculateMA(30,values),
smooth: true,
showSymbol: false,
lineStyle: {
opacity: 0.5
}
},
{
name: '成交量(手)',
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 1,
itemStyle: {
color: '#7fbe9e'
},
data: volumns
}
]
};
chart.setOption(option);
})
}
function showK(code,name){
data.code=code
data.name=name
@ -602,9 +918,9 @@ function showK(code,name){
if(code.startsWith('gb_')){
data.kURL='http://image.sinajs.cn/newchart/usstock/daily/'+data.code.replace("gb_","")+'.gif'+"?t="+Date.now()
}
modalShow3.value=true
//https://image.sinajs.cn/newchart/usstock/daily/dji.gif
//https://image.sinajs.cn/newchart/hk_stock/daily/06030.gif?1740729404273
modalShow3.value=true
}
@ -1190,8 +1506,9 @@ function delStockGroup(code,name,groupId){
<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 v-model:show="modalShow3" :title="data.name" style="width: 900px" :preset="'card'" @after-enter="handleKLine">
<!-- <n-image :src="data.kURL" />-->
<div ref="kLineChartRef" style="width: 850px; height: 500px;"></div>
</n-modal>
<n-modal transform-origin="center" v-model:show="modalShow4" preset="card" style="width: 800px;" :title="'['+data.name+']AI分析结果'" >

View File

@ -35,6 +35,8 @@ export function GetGroupStockList(arg1:number):Promise<Array<data.GroupStock>>;
export function GetPromptTemplates(arg1:string,arg2:string):Promise<any>;
export function GetStockKLine(arg1:string,arg2:string,arg3:number):Promise<any>;
export function GetStockList(arg1:string):Promise<Array<data.StockBasic>>;
export function GetVersionInfo():Promise<models.VersionInfo>;

View File

@ -66,6 +66,10 @@ export function GetPromptTemplates(arg1, arg2) {
return window['go']['main']['App']['GetPromptTemplates'](arg1, arg2);
}
export function GetStockKLine(arg1, arg2, arg3) {
return window['go']['main']['App']['GetStockKLine'](arg1, arg2, arg3);
}
export function GetStockList(arg1) {
return window['go']['main']['App']['GetStockList'](arg1);
}