1
0

feat: 增加股票现价显示,日线改为后复权数据

This commit is contained in:
2025-10-11 15:01:35 +08:00
parent 69020852b9
commit 5390a879e7
6 changed files with 167 additions and 117 deletions

View File

@@ -42,6 +42,20 @@ public class NumberHelper {
return NumberUtil.decimalFormat("0.00%", value);
}
public static String formatPriceDouble(Double value) {
if (ObjectUtil.isNull(value)) {
return null;
}
return NumberUtil.decimalFormat("0.00", value);
}
public static String formatPriceDouble(Integer value) {
if (ObjectUtil.isNull(value)) {
return null;
}
return NumberUtil.decimalFormat("0.00", value);
}
public static Double parseDouble(String value) {
if (StrUtil.isBlank(value)) {
return null;

View File

@@ -23,6 +23,9 @@ public interface DailyRepository extends SimpleRepository<Daily> {
@Query("select min(daily.tradeDate) from Daily daily")
LocalDate findMinTradeDate();
@Query("from Daily daily where daily.stock.id = ?1 order by daily.tradeDate desc limit 1")
Optional<Daily> findLatest(Long stockId);
@EntityGraph(attributePaths = {"stock"})
@Override
Optional<Daily> findOne(Predicate predicate);

View File

@@ -58,4 +58,8 @@ public class StockService extends SimpleServiceSupport<Stock> {
Sort.by(Sort.Direction.ASC, Daily_.TRADE_DATE)
);
}
public Optional<Daily> findDailyLatest(Long stockId) {
return dailyRepository.findLatest(stockId);
}
}

View File

@@ -1,6 +1,7 @@
package com.lanyuanxiaoyao.leopard.server.controller;
import cn.hutool.core.bean.BeanUtil;
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.helper.NumberHelper;
import com.lanyuanxiaoyao.leopard.core.service.StockService;
@@ -108,6 +109,18 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockD
));
}
@GetMapping("daily/current/{id}")
public GlobalResponse<Map<String, Object>> dailyCurrent(@PathVariable("id") Long id) {
var daily = stockService.findDailyLatest(id);
return GlobalResponse.responseMapData(Map.of(
"date", daily.map(Daily::getTradeDate).map(LocalDate::toString).orElse("/"),
"open", daily.map(Daily::getOpen).map(NumberHelper::formatPriceDouble).orElse(NumberHelper.FINANCE_NULL_DOUBLE),
"close", daily.map(Daily::getClose).map(NumberHelper::formatPriceDouble).orElse(NumberHelper.FINANCE_NULL_DOUBLE),
"low", daily.map(Daily::getLow).map(NumberHelper::formatPriceDouble).orElse(NumberHelper.FINANCE_NULL_DOUBLE),
"high", daily.map(Daily::getHigh).map(NumberHelper::formatPriceDouble).orElse(NumberHelper.FINANCE_NULL_DOUBLE)
));
}
@GetMapping("daily/{id}")
public GlobalResponse<Map<String, Object>> dailyCharts(@PathVariable("id") Long id) {
var data = stockService.findDailyRecent(id, 100);
@@ -115,7 +128,7 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockD
var yList = new ArrayList<List<Double>>();
for (var daily : data) {
xList.add(daily.getTradeDate().toString());
yList.add(List.of(daily.getOpen(), daily.getClose(), daily.getLow(), daily.getHigh()));
yList.add(List.of(daily.getHfqOpen(), daily.getHfqClose(), daily.getHfqLow(), daily.getHfqHigh()));
}
return GlobalResponse.responseMapData(Map.of(
"xList", xList, "yList", yList

View File

@@ -6,7 +6,6 @@ import Overview from './pages/Overview.tsx'
import Root from './pages/Root.tsx'
import Test from './pages/Test.tsx'
import StockList from './pages/stock/StockList.tsx'
import StockDetail from './pages/stock/StockDetail.tsx'
import TaskList from './pages/task/TaskList.tsx'
import TaskTemplateList from './pages/task/TaskTemplateList.tsx'
import TaskScheduleList from './pages/task/TaskScheduleList.tsx'
@@ -35,10 +34,6 @@ const routes: RouteObject[] = [
path: 'list',
Component: StockList,
},
{
path: 'detail/:id',
Component: StockDetail,
},
{
path: "collection",
children: [

View File

@@ -571,7 +571,7 @@ export function stockListColumns(idField: string = 'id', extraColumns: Array<Col
{
name: 'name',
label: '简称',
width: 150,
width: 100,
},
{
name: 'fullname',
@@ -782,37 +782,61 @@ export function stockListColumns(idField: string = 'id', extraColumns: Array<Col
},
],
},
{type: 'divider'},
"100日线数据",
],
},
{type: 'divider'},
{
type: 'service',
api: `get:${commonInfo.baseUrl}/stock/daily/current/\${${idField}}`,
body: [
"现价 (${date})",
{
type: 'chart',
height: 500,
api: `get:${commonInfo.baseUrl}/stock/daily/\${${idField}}`,
config: {
backgroundColor: '#fff',
animation: true,
animationDuration: 1000,
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
},
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderColor: '#333',
borderWidth: 1,
textStyle: {
color: '#fff',
fontSize: 12,
},
padding: 12,
formatter: function (params: any) {
const param = params[0]
const open = toNumber(param.data[1]).toFixed(2)
const close = toNumber(param.data[2]).toFixed(2)
const lowest = toNumber(param.data[3]).toFixed(2)
const highest = toNumber(param.data[4]).toFixed(2)
className: 'my-2',
type: 'property',
column: 4,
items: [
{label: '开盘价', content: '${open}'},
{label: '收盘价', content: '${close}'},
{label: '最高价', content: '${high}'},
{label: '最低价', content: '${low}'},
],
},
],
},
{
type: 'chart',
title: '100日线数据',
height: 500,
api: `get:${commonInfo.baseUrl}/stock/daily/\${${idField}}`,
config: {
title: {
text: '100日线数据',
subtext: '后复权数据',
},
backgroundColor: '#fff',
animation: true,
animationDuration: 1000,
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
},
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderColor: '#333',
borderWidth: 1,
textStyle: {
color: '#fff',
fontSize: 12,
},
padding: 12,
formatter: function (params: any) {
const param = params[0]
const open = toNumber(param.data[1]).toFixed(2)
const close = toNumber(param.data[2]).toFixed(2)
const lowest = toNumber(param.data[3]).toFixed(2)
const highest = toNumber(param.data[4]).toFixed(2)
return `<div class="text-center font-bold mb-2">${param.name}</div>
return `<div class="text-center font-bold mb-2">${param.name}</div>
<div class="text-center">
<span>开盘:</span>
<span class="font-bold ml-4">${open}</span>
@@ -829,89 +853,86 @@ export function stockListColumns(idField: string = 'id', extraColumns: Array<Col
<span>最高:</span>
<span class="font-bold ml-4">${highest}</span>
</div>`
},
},
grid: {
left: '10%',
right: '10%',
top: '10%',
bottom: '15%',
containLabel: true,
},
xAxis: {
data: '${xList || []}',
axisLine: {
lineStyle: {
color: '#e0e0e0',
},
},
axisLabel: {
color: '#666',
fontWeight: 'bold',
},
splitLine: {
show: false,
},
axisTick: {
show: false,
},
},
yAxis: {
scale: true,
axisLine: {
lineStyle: {
color: '#e0e0e0',
},
},
axisLabel: {
color: '#666',
fontWeight: 'bold',
formatter: function (value: number) {
return value.toFixed(2)
},
},
splitLine: {
lineStyle: {
type: 'dashed',
color: '#f0f0f0',
},
},
axisTick: {
show: false,
},
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 100,
},
{
show: true,
type: 'slider',
top: '90%',
start: 0,
end: 100,
},
],
series: [
{
type: 'candlestick',
data: '${yList || []}',
itemStyle: {
color: '#eb5454',
color0: '#4aaa93',
borderColor: '#eb5454',
borderColor0: '#4aaa93',
borderWidth: 1,
},
},
],
},
},
"12月线数据",
],
grid: {
left: '2%',
right: '2%',
top: '15%',
bottom: '15%',
containLabel: true,
},
xAxis: {
data: '${xList || []}',
axisLine: {
lineStyle: {
color: '#e0e0e0',
},
},
axisLabel: {
color: '#666',
fontWeight: 'bold',
},
splitLine: {
show: false,
},
axisTick: {
show: false,
},
},
yAxis: {
scale: true,
axisLine: {
lineStyle: {
color: '#e0e0e0',
},
},
axisLabel: {
color: '#666',
fontWeight: 'bold',
formatter: function (value: number) {
return value.toFixed(2)
},
},
splitLine: {
lineStyle: {
type: 'dashed',
color: '#f0f0f0',
},
},
axisTick: {
show: false,
},
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 100,
},
{
show: true,
type: 'slider',
top: '90%',
start: 0,
end: 100,
},
],
series: [
{
type: 'candlestick',
data: '${yList || []}',
itemStyle: {
color: '#eb5454',
color0: '#4aaa93',
borderColor: '#eb5454',
borderColor0: '#4aaa93',
borderWidth: 1,
},
},
],
},
},
],
},