1
0

feat: 增加财务报表近5年图表展示

This commit is contained in:
2025-09-12 17:46:56 +08:00
parent 338554c523
commit cfc71f83a1
4 changed files with 254 additions and 10 deletions

View File

@@ -1,11 +1,14 @@
package com.lanyuanxiaoyao.leopard.server.controller; package com.lanyuanxiaoyao.leopard.server.controller;
import cn.hutool.core.bean.BeanUtil;
import com.lanyuanxiaoyao.leopard.core.entity.Stock; import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.server.helper.NumberHelper; import com.lanyuanxiaoyao.leopard.server.helper.NumberHelper;
import com.lanyuanxiaoyao.leopard.server.service.StockService; import com.lanyuanxiaoyao.leopard.server.service.StockService;
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse; import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport; import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@@ -44,7 +47,11 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
balanceSheet balanceSheet
.map(bs -> new BalanceSheetItem( .map(bs -> new BalanceSheetItem(
NumberHelper.formatFinanceDouble(bs.getTotalAssets()), NumberHelper.formatFinanceDouble(bs.getTotalAssets()),
NumberHelper.formatFinanceDouble(bs.getTotalLiabilities()) NumberHelper.formatFinanceDouble(bs.getTotalCurrentAssets()),
NumberHelper.formatFinanceDouble(bs.getTotalNonCurrentAssets()),
NumberHelper.formatFinanceDouble(bs.getTotalLiabilities()),
NumberHelper.formatFinanceDouble(bs.getTotalCurrentLiabilities()),
NumberHelper.formatFinanceDouble(bs.getTotalNonCurrentLiabilities())
)) ))
.orElse(new BalanceSheetItem()), .orElse(new BalanceSheetItem()),
income income
@@ -62,6 +69,24 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
)); ));
} }
private GlobalResponse<Map<String, Object>> convertFinanceChartData(List<?> data, String field) {
return GlobalResponse.responseDetailData(
data.stream()
.map(item -> BeanUtil.getFieldValue(item, field))
.toList()
);
}
@GetMapping("finance/{id}/{type}/{field}")
public GlobalResponse<Map<String, Object>> financeCharts(@PathVariable("id") Long id, @PathVariable("type") String type, @PathVariable("field") String field) {
return switch (type) {
case "balanceSheet" -> convertFinanceChartData(stockService.findBalanceSheetRecent(id, 5), field);
case "income" -> convertFinanceChartData(stockService.findIncomeRecent(id, 5), field);
case "cashflow" -> convertFinanceChartData(stockService.findCashFlowRecent(id, 5), field);
default -> throw new IllegalStateException("Unexpected value: " + type);
};
}
@Override @Override
protected Function<Void, Stock> saveItemMapper() { protected Function<Void, Stock> saveItemMapper() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
@@ -111,10 +136,18 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
public record BalanceSheetItem( public record BalanceSheetItem(
String totalAssets, String totalAssets,
String totalLiabilities String totalCurrentAssets,
String totalNonCurrentAssets,
String totalLiabilities,
String totalCurrentLiabilities,
String totalNonCurrentLiabilities
) { ) {
public BalanceSheetItem() { public BalanceSheetItem() {
this( this(
NumberHelper.FINANCE_NULL_DOUBLE,
NumberHelper.FINANCE_NULL_DOUBLE,
NumberHelper.FINANCE_NULL_DOUBLE,
NumberHelper.FINANCE_NULL_DOUBLE,
NumberHelper.FINANCE_NULL_DOUBLE, NumberHelper.FINANCE_NULL_DOUBLE,
NumberHelper.FINANCE_NULL_DOUBLE NumberHelper.FINANCE_NULL_DOUBLE
); );

View File

@@ -1,5 +1,6 @@
package com.lanyuanxiaoyao.leopard.server.helper; package com.lanyuanxiaoyao.leopard.server.helper;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import java.util.function.Function; import java.util.function.Function;
@@ -15,7 +16,7 @@ public class NumberHelper {
if (ObjectUtil.isNull(value)) { if (ObjectUtil.isNull(value)) {
return FINANCE_NULL_DOUBLE; return FINANCE_NULL_DOUBLE;
} }
return value.toString(); return NumberUtil.decimalFormat("#.##", value);
} }
public static Double parseDouble(String value) { public static Double parseDouble(String value) {

View File

@@ -1,8 +1,11 @@
package com.lanyuanxiaoyao.leopard.server.service; package com.lanyuanxiaoyao.leopard.server.service;
import com.lanyuanxiaoyao.leopard.core.entity.BalanceSheet; import com.lanyuanxiaoyao.leopard.core.entity.BalanceSheet;
import com.lanyuanxiaoyao.leopard.core.entity.BalanceSheet_;
import com.lanyuanxiaoyao.leopard.core.entity.CashFlow; import com.lanyuanxiaoyao.leopard.core.entity.CashFlow;
import com.lanyuanxiaoyao.leopard.core.entity.CashFlow_;
import com.lanyuanxiaoyao.leopard.core.entity.Income; import com.lanyuanxiaoyao.leopard.core.entity.Income;
import com.lanyuanxiaoyao.leopard.core.entity.Income_;
import com.lanyuanxiaoyao.leopard.core.entity.QBalanceSheet; import com.lanyuanxiaoyao.leopard.core.entity.QBalanceSheet;
import com.lanyuanxiaoyao.leopard.core.entity.QCashFlow; import com.lanyuanxiaoyao.leopard.core.entity.QCashFlow;
import com.lanyuanxiaoyao.leopard.core.entity.QIncome; import com.lanyuanxiaoyao.leopard.core.entity.QIncome;
@@ -12,8 +15,11 @@ import com.lanyuanxiaoyao.leopard.core.repository.CashFlowRepository;
import com.lanyuanxiaoyao.leopard.core.repository.IncomeRepository; import com.lanyuanxiaoyao.leopard.core.repository.IncomeRepository;
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository; import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport; import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /**
@@ -56,4 +62,31 @@ public class StockService extends SimpleServiceSupport<Stock> {
.and(QCashFlow.cashFlow.stock.id.eq(stockId)) .and(QCashFlow.cashFlow.stock.id.eq(stockId))
); );
} }
public List<BalanceSheet> findBalanceSheetRecent(Long stockId, int years) {
var current = LocalDate.now();
return balanceSheetRepository.findAll(
QBalanceSheet.balanceSheet.stock.id.eq(stockId)
.and(QBalanceSheet.balanceSheet.year.between(current.minusYears(years).getYear(), current.getYear())),
Sort.by(Sort.Direction.ASC, BalanceSheet_.YEAR)
);
}
public List<Income> findIncomeRecent(Long stockId, int years) {
var current = LocalDate.now();
return incomeRepository.findAll(
QIncome.income.stock.id.eq(stockId)
.and(QIncome.income.year.between(current.minusYears(years).getYear(), current.getYear())),
Sort.by(Sort.Direction.ASC, Income_.YEAR)
);
}
public List<CashFlow> findCashFlowRecent(Long stockId, int years) {
var current = LocalDate.now();
return cashFlowRepository.findAll(
QCashFlow.cashFlow.stock.id.eq(stockId)
.and(QCashFlow.cashFlow.year.between(current.minusYears(years).getYear(), current.getYear())),
Sort.by(Sort.Direction.ASC, CashFlow_.YEAR)
);
}
} }

View File

@@ -1,6 +1,149 @@
import React from 'react' import React from 'react'
import {useParams} from 'react-router' import {useParams} from 'react-router'
import {amisRender, commonInfo, remoteMappings} from '../../util/amis.tsx' import {amisRender, commonInfo, readOnlyDialogOptions, remoteMappings} from '../../util/amis.tsx'
import type {Schema} from 'amis'
const financePropertyLabel = (id: string | undefined, label: string, type: string, field: string): Schema => {
if (!id) {
return {
type: 'tpl',
tpl: label,
}
}
let current = new Date().getFullYear()
return {
type: 'wrapper',
size: 'none',
body: [
label,
{
className: 'ml-1 text-secondary',
type: 'action',
label: '',
icon: 'fa fa-eye',
level: 'link',
size: 'xs',
tooltip: '查看五年趋势',
tooltipPlacement: 'top',
actionType: 'dialog',
dialog: {
title: `${label}五年趋势`,
size: 'md',
bodyClassName: 'p-0',
...readOnlyDialogOptions(),
body: {
type: 'chart',
api: `get:${commonInfo.baseUrl}/stock/finance/${id}/${type}/${field}`,
height: 500,
config: {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
borderColor: '#ccc',
borderWidth: 1,
textStyle: {
color: '#333',
},
padding: [10, 15],
},
grid: {
left: '5%',
right: '5%',
top: '10%',
bottom: '15%',
containLabel: true,
},
xAxis: {
type: 'category',
data: [
current - 5,
current - 4,
current - 3,
current - 2,
current - 1,
],
axisLine: {
lineStyle: {
color: '#e0e0e0',
},
},
axisLabel: {
color: '#666',
fontWeight: 'bold',
},
axisTick: {
show: false,
},
},
yAxis: {
type: 'value',
show: true,
splitLine: {
lineStyle: {
type: 'dashed',
color: '#f0f0f0',
},
},
axisLine: {
show: false,
},
axisLabel: {
color: '#999',
fontSize: 12,
},
axisTick: {
show: false,
},
},
series: [
{
data: '${detail || []}',
type: 'line',
smooth: true,
showSymbol: true,
symbolSize: 6,
lineStyle: {
width: 3,
color: '#4096ff',
shadowColor: 'rgba(64, 150, 255, 0.3)',
shadowBlur: 5,
shadowOffsetY: 2,
},
itemStyle: {
color: '#4096ff',
borderWidth: 2,
borderColor: '#fff',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: 'rgba(64, 150, 255, 0.2)',
}, {
offset: 1, color: 'rgba(64, 150, 255, 0.01)',
}],
},
},
label: {
show: true,
position: 'top',
color: '#333',
fontWeight: 'bold',
fontSize: 12,
},
},
],
},
},
},
},
],
}
}
function StockDetail() { function StockDetail() {
const {id} = useParams() const {id} = useParams()
@@ -39,8 +182,30 @@ function StockDetail() {
className: 'my-2', className: 'my-2',
type: 'property', type: 'property',
items: [ items: [
{label: '总资产', content: '${balanceSheet.totalAssets}'}, {
{label: '总负债', content: '${balanceSheet.totalLiabilities}'}, label: financePropertyLabel(id, '总资产', 'balanceSheet', 'totalAssets'),
content: '${balanceSheet.totalAssets}',
},
{
label: financePropertyLabel(id, '流动资产', 'balanceSheet', 'totalCurrentAssets'),
content: '${balanceSheet.totalCurrentAssets}',
},
{
label: financePropertyLabel(id, '非流动资产', 'balanceSheet', 'totalNonCurrentAssets'),
content: '${balanceSheet.totalNonCurrentAssets}',
},
{
label: financePropertyLabel(id, '总负债', 'balanceSheet', 'totalLiabilities'),
content: '${balanceSheet.totalLiabilities}',
},
{
label: financePropertyLabel(id, '流动负债', 'balanceSheet', 'totalCurrentLiabilities'),
content: '${balanceSheet.totalCurrentLiabilities}',
},
{
label: financePropertyLabel(id, '非流动负债', 'balanceSheet', 'totalNonCurrentLiabilities'),
content: '${balanceSheet.totalNonCurrentLiabilities}',
},
], ],
}, },
'利润表', '利润表',
@@ -48,9 +213,18 @@ function StockDetail() {
className: 'my-2', className: 'my-2',
type: 'property', type: 'property',
items: [ items: [
{label: '营业总收入', content: '${income.totalOperatingRevenue}'}, {
{label: '营业总成本', content: '${income.totalOperatingCost}'}, label: financePropertyLabel(id, '营业总收入', 'income', 'totalOperatingRevenue'),
{label: '营业总利润', content: '${income.totalProfit}'}, content: '${income.totalOperatingRevenue}',
},
{
label: financePropertyLabel(id, '营业总成本', 'income', 'totalOperatingCost'),
content: '${income.totalOperatingCost}',
},
{
label: financePropertyLabel(id, '营业总利润', 'income', 'totalProfit'),
content: '${income.totalProfit}',
},
], ],
}, },
'现金流量表', '现金流量表',
@@ -58,7 +232,10 @@ function StockDetail() {
className: 'my-2', className: 'my-2',
type: 'property', type: 'property',
items: [ items: [
{label: '净利润', content: '${cashFlow.netProfit}'}, {
label: financePropertyLabel(id, '净利润', 'cashflow', 'netProfit'),
content: '${cashFlow.netProfit}',
},
], ],
}, },
{type: 'divider'}, {type: 'divider'},