From cfc71f83a1c79a74e4fc14290000a71d48c6af75 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Fri, 12 Sep 2025 17:46:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=B4=A2=E5=8A=A1?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E8=BF=915=E5=B9=B4=E5=9B=BE=E8=A1=A8?= =?UTF-8?q?=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/controller/StockController.java | 37 +++- .../leopard/server/helper/NumberHelper.java | 3 +- .../leopard/server/service/StockService.java | 33 +++ leopard-web/src/pages/stock/StockDetail.tsx | 191 +++++++++++++++++- 4 files changed, 254 insertions(+), 10 deletions(-) diff --git a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/StockController.java b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/StockController.java index 418bc61..9b55289 100644 --- a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/StockController.java +++ b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/StockController.java @@ -1,11 +1,14 @@ package com.lanyuanxiaoyao.leopard.server.controller; +import cn.hutool.core.bean.BeanUtil; import com.lanyuanxiaoyao.leopard.core.entity.Stock; import com.lanyuanxiaoyao.leopard.server.helper.NumberHelper; import com.lanyuanxiaoyao.leopard.server.service.StockService; import com.lanyuanxiaoyao.service.template.controller.GlobalResponse; import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport; import java.time.LocalDate; +import java.util.List; +import java.util.Map; import java.util.function.Function; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -44,7 +47,11 @@ public class StockController extends SimpleControllerSupport new BalanceSheetItem( 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()), income @@ -62,6 +69,24 @@ public class StockController extends SimpleControllerSupport> convertFinanceChartData(List data, String field) { + return GlobalResponse.responseDetailData( + data.stream() + .map(item -> BeanUtil.getFieldValue(item, field)) + .toList() + ); + } + + @GetMapping("finance/{id}/{type}/{field}") + public GlobalResponse> 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 protected Function saveItemMapper() { throw new UnsupportedOperationException(); @@ -111,10 +136,18 @@ public class StockController extends SimpleControllerSupport { .and(QCashFlow.cashFlow.stock.id.eq(stockId)) ); } + + public List 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 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 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) + ); + } } diff --git a/leopard-web/src/pages/stock/StockDetail.tsx b/leopard-web/src/pages/stock/StockDetail.tsx index cc8308f..4a7d019 100644 --- a/leopard-web/src/pages/stock/StockDetail.tsx +++ b/leopard-web/src/pages/stock/StockDetail.tsx @@ -1,6 +1,149 @@ import React from 'react' 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() { const {id} = useParams() @@ -39,8 +182,30 @@ function StockDetail() { className: 'my-2', type: 'property', 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', type: 'property', items: [ - {label: '营业总收入', content: '${income.totalOperatingRevenue}'}, - {label: '营业总成本', content: '${income.totalOperatingCost}'}, - {label: '营业总利润', content: '${income.totalProfit}'}, + { + label: financePropertyLabel(id, '营业总收入', 'income', 'totalOperatingRevenue'), + 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', type: 'property', items: [ - {label: '净利润', content: '${cashFlow.netProfit}'}, + { + label: financePropertyLabel(id, '净利润', 'cashflow', 'netProfit'), + content: '${cashFlow.netProfit}', + }, ], }, {type: 'divider'},