From 69020852b9a0e8c14764c8705cced1a3ccac0b65 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Fri, 10 Oct 2025 23:13:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8dialog=E4=BB=A3?= =?UTF-8?q?=E6=9B=BF=E8=82=A1=E7=A5=A8=E8=AF=A6=E6=83=85=E8=B7=B3=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/stock/StockCollectionDetail.tsx | 15 +- leopard-web/src/pages/stock/StockList.tsx | 4 +- leopard-web/src/util/amis.tsx | 549 +++++++++++++++++- 3 files changed, 543 insertions(+), 25 deletions(-) diff --git a/leopard-web/src/pages/stock/StockCollectionDetail.tsx b/leopard-web/src/pages/stock/StockCollectionDetail.tsx index 0143d1e..3f9f94e 100644 --- a/leopard-web/src/pages/stock/StockCollectionDetail.tsx +++ b/leopard-web/src/pages/stock/StockCollectionDetail.tsx @@ -1,9 +1,8 @@ import React from "react" import {amisRender, commonInfo, crudCommonOptions, paginationTemplate, stockListColumns} from '../../util/amis.tsx' -import {useNavigate, useParams} from 'react-router' +import {useParams} from 'react-router' function StockCollectionDetail() { - const navigate = useNavigate() const {id} = useParams() return (
@@ -19,19 +18,19 @@ function StockCollectionDetail() { ...crudCommonOptions(), ...paginationTemplate(100, undefined, ['filter-toggler']), columns: stockListColumns( - navigate, + undefined, [ { name: 'score', label: '得分', width: 50, align: 'center', - } - ] + }, + ], ), - } - ] - } + }, + ], + }, )}
) diff --git a/leopard-web/src/pages/stock/StockList.tsx b/leopard-web/src/pages/stock/StockList.tsx index 20d386a..070047f 100644 --- a/leopard-web/src/pages/stock/StockList.tsx +++ b/leopard-web/src/pages/stock/StockList.tsx @@ -7,10 +7,8 @@ import { remoteOptions, stockListColumns, } from '../../util/amis.tsx' -import {useNavigate} from 'react-router' function StockList() { - const navigate = useNavigate() return (
{amisRender( @@ -96,7 +94,7 @@ function StockList() { }, ], }, - columns: stockListColumns(navigate), + columns: stockListColumns(), }, ], }, diff --git a/leopard-web/src/util/amis.tsx b/leopard-web/src/util/amis.tsx index 7306e62..32a0a20 100644 --- a/leopard-web/src/util/amis.tsx +++ b/leopard-web/src/util/amis.tsx @@ -4,9 +4,10 @@ import 'amis/lib/helper.css' import 'amis/sdk/iconfont.css' import '@fortawesome/fontawesome-free/css/all.min.css' import axios from 'axios' -import {isEqual} from 'es-toolkit' -import type {NavigateFunction} from 'react-router' +import {isEqual, isNil} from 'es-toolkit' +// @ts-ignore import type {ColumnSchema} from 'amis/lib/renderers/Table2' +import {toNumber} from 'es-toolkit/compat' export const commonInfo = { debug: isEqual(import.meta.env.MODE, 'development'), @@ -337,7 +338,230 @@ export function remoteMappings(name: string, field: string) { } } -export function stockListColumns(navigate: NavigateFunction, extraColumns: Array = []) { +const formatFinanceNumber = (value: number): string => { + if (isNil(value)) { + return '-' + } + + const isNegative = value < 0 + const absoluteValue = Math.abs(value) + + let formatted: string + if (absoluteValue >= 100000000) { + formatted = (absoluteValue / 100000000).toFixed(2) + '亿' + } else if (absoluteValue >= 10000) { + formatted = (absoluteValue / 10000).toFixed(2) + '万' + } else { + formatted = absoluteValue.toLocaleString() + } + + return isNegative ? `-${formatted}` : formatted +} + +const formatDaysNumber = (value: number): string => { + if (isNil(value)) { + return '-' + } + return `${value.toFixed(0)}天` +} + +const formatPercentageNumber = (value: number): string => { + if (isNil(value)) { + return '-' + } + return `${(value * 100).toFixed(2)}%` +} + +type FinanceType = 'PERCENTAGE' | 'FINANCE' | 'DAYS' + +const financePropertyLabel = (idField: string, label: string, type: FinanceType, field: string): Schema => { + let formatter: (value: number) => string + switch (type) { + case 'PERCENTAGE': + formatter = formatPercentageNumber + break + case 'FINANCE': + formatter = formatFinanceNumber + break + case 'DAYS': + formatter = formatDaysNumber + break + default: + formatter = (v: number) => v.toFixed(2) + } + return { + type: 'wrapper', + size: 'none', + body: [ + { + visibleOn: `\${!${idField}}`, + type: 'tpl', + tpl: label, + }, + { + visibleOn: `\${${idField}}`, + className: 'text-current font-bold', + type: 'action', + label: label, + level: 'link', + tooltip: '这是什么?', + tooltipPlacement: 'top', + actionType: 'dialog', + dialog: { + title: '', + size: 'lg', + ...readOnlyDialogOptions(), + actions: [ + { + type: 'action', + label: '新页面打开', + icon: 'fa fa-solid fa-arrow-up-right-from-square', + actionType: 'url', + url: `https://zh.wikipedia.org/wiki/${label}`, + blank: true, + }, + ], + body: { + type: 'iframe', + src: `https://zh.wikipedia.org/wiki/${label}`, + height: 800, + }, + }, + }, + { + className: 'text-secondary', + type: 'action', + label: '', + icon: 'fa fa-eye', + level: 'link', + size: 'xs', + tooltip: '查看五年趋势', + tooltipPlacement: 'top', + actionType: 'dialog', + dialog: { + title: `${label}五年趋势`, + size: 'lg', + bodyClassName: 'p-0', + ...readOnlyDialogOptions(), + body: { + type: 'chart', + api: `get:${commonInfo.baseUrl}/stock/finance/\${${idField}}/${field}`, + height: 500, + config: { + tooltip: { + trigger: 'axis', + backgroundColor: 'rgba(255, 255, 255, 0.9)', + borderColor: '#ccc', + borderWidth: 1, + textStyle: { + color: '#333', + }, + padding: [10, 15], + formatter: (params: any) => { + const item = params[0] + return `${item.name}
${item.marker}${formatter(item.value)}` + }, + }, + grid: { + left: '5%', + right: '5%', + top: '10%', + bottom: '15%', + containLabel: true, + }, + xAxis: { + type: 'category', + data: '${xList || []}', + 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, + formatter: (value: number) => { + return formatter(value) + }, + }, + axisTick: { + show: false, + }, + }, + series: [ + { + data: '${yList || []}', + 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, + formatter: (params: any) => { + return formatter(params.value) + }, + }, + }, + ], + }, + }, + }, + }, + ], + } +} + +export function stockListColumns(idField: string = 'id', extraColumns: Array = []) { return [ { name: 'code', @@ -381,18 +605,315 @@ export function stockListColumns(navigate: NavigateFunction, extraColumns: Array type: 'action', label: '详情', level: 'link', - onEvent: { - click: { - actions: [ - { - actionType: 'custom', - // @ts-ignore - script: (context, action, event) => { - navigate(`/stock/detail/${context.props.data['id']}`) + actionType: 'dialog', + dialog: { + title: '股票详情', + size: 'full', + ...readOnlyDialogOptions(), + body: [ + { + type: 'property', + items: [ + {label: '编码', content: '${code}'}, + {label: '名称', content: '${name}'}, + {label: '全名', content: '${fullname}'}, + { + label: '市场', + content: { + ...remoteMappings('stock_market', 'market'), + value: '${market}', + }, }, - }, - ], - }, + {label: '行业', content: '${industry}'}, + {label: '上市日期', content: '${listedDate}'}, + ], + }, + {type: 'divider'}, + { + type: 'service', + api: `get:${commonInfo.baseUrl}/stock/finance/\${${idField}}`, + body: [ + '资产负债表', + { + className: 'my-2', + type: 'property', + column: 4, + items: [ + { + label: financePropertyLabel(idField, '总资产', 'FINANCE', 'totalAssets'), + content: '${balanceSheet.totalAssets}', + span: 2, + }, + { + label: financePropertyLabel(idField, '总负债', 'FINANCE', 'totalLiabilities'), + content: '${balanceSheet.totalLiabilities}', + span: 2, + }, + { + label: financePropertyLabel(idField, '流动资产', 'FINANCE', 'currentAssets'), + content: '${balanceSheet.currentAssets}', + }, + { + label: financePropertyLabel(idField, '流动资产占比', 'PERCENTAGE', 'currentAssetsToTotalAssetsRatio'), + content: '${balanceSheet.currentAssetsRatio}', + }, + { + label: financePropertyLabel(idField, '流动负债', 'FINANCE', 'currentLiabilities'), + content: '${balanceSheet.currentLiabilities}', + }, + { + label: financePropertyLabel(idField, '流动负债占比', 'PERCENTAGE', 'currentLiabilitiesToTotalAssetsRatio'), + content: '${balanceSheet.currentLiabilitiesRatio}', + }, + { + label: financePropertyLabel(idField, '非流动资产', 'FINANCE', 'fixedAssets'), + content: '${balanceSheet.fixedAssets}', + }, + { + label: financePropertyLabel(idField, '非流动资产占比', 'PERCENTAGE', 'fixedAssetsToTotalAssetsRatio'), + content: '${balanceSheet.fixedAssetsRatio}', + }, + { + label: financePropertyLabel(idField, '非流动负债', 'FINANCE', 'longTermLiabilities'), + content: '${balanceSheet.longTermLiabilities}', + }, + { + label: financePropertyLabel(idField, '非流动负债占比', 'PERCENTAGE', 'longTermLiabilitiesToTotalAssetsRatio'), + content: '${balanceSheet.longTermLiabilitiesRatio}', + }, + ], + }, + '利润表', + { + className: 'my-2', + type: 'property', + items: [ + { + label: financePropertyLabel(idField, '营业收入', 'FINANCE', 'operatingRevenue'), + content: '${income.operatingRevenue}', + }, + { + label: financePropertyLabel(idField, '营业成本', 'FINANCE', 'operatingCost'), + content: '${income.operatingCost}', + }, + { + label: financePropertyLabel(idField, '营业利润', 'FINANCE', 'operatingProfit'), + content: '${income.operatingProfit}', + }, + ], + }, + '现金流量表', + { + className: 'my-2', + type: 'property', + items: [ + { + label: financePropertyLabel(idField, '净利润', 'FINANCE', 'netProfit'), + content: '${cashFlow.netProfit}', + span: 3, + }, + { + label: financePropertyLabel(idField, '营业活动现金流量', 'FINANCE', 'cashFlowFromOperatingActivities'), + content: '${cashFlow.cashFlowFromOperatingActivities}', + }, + { + label: financePropertyLabel(idField, '投资活动现金流量', 'FINANCE', 'cashFlowFromInvestingActivities'), + content: '${cashFlow.cashFlowFromInvestingActivities}', + }, + { + label: financePropertyLabel(idField, '筹资活动现金流量', 'FINANCE', 'cashFlowFromFinancingActivities'), + content: '${cashFlow.cashFlowFromFinancingActivities}', + }, + ], + }, + '财务指标', + { + className: 'my-2', + type: 'property', + column: 4, + items: [ + { + label: financePropertyLabel(idField, '流动比率', 'FINANCE', 'currentRatio'), + content: '${indicate.currentRatio}', + }, + { + label: financePropertyLabel(idField, '速动比率', 'FINANCE', 'quickRatio'), + content: '${indicate.quickRatio}', + }, + { + label: financePropertyLabel(idField, 'ROE', 'FINANCE', 'returnOnEquity'), + content: '${indicate.roe}', + }, + { + label: financePropertyLabel(idField, 'ROA', 'FINANCE', 'returnOnAssets'), + content: '${indicate.roa}', + }, + { + label: financePropertyLabel(idField, '应收账款周转率', 'FINANCE', 'accountsReceivableTurnover'), + content: '${indicate.accountsReceivableTurnover}', + }, + { + label: financePropertyLabel(idField, '应收账款周转天数', 'DAYS', 'daysAccountsReceivableTurnover'), + content: '${indicate.daysAccountsReceivableTurnover}', + }, + { + label: financePropertyLabel(idField, '存货周转率', 'FINANCE', 'inventoryTurnover'), + content: '${indicate.inventoryTurnover}', + }, + { + label: financePropertyLabel(idField, '存货周转天数', 'DAYS', 'daysInventoryTurnover'), + content: '${indicate.daysInventoryTurnover}', + }, + { + label: financePropertyLabel(idField, '固定资产周转率', 'FINANCE', 'fixedAssetsTurnover'), + content: '${indicate.fixedAssetsTurnover}', + }, + { + label: financePropertyLabel(idField, '固定资产周转天数', 'DAYS', 'daysFixedAssetsTurnover'), + content: '${indicate.daysFixedAssetsTurnover}', + }, + { + label: financePropertyLabel(idField, '总资产周转率', 'FINANCE', 'totalAssetsTurnover'), + content: '${indicate.totalAssetsTurnover}', + }, + { + label: financePropertyLabel(idField, '总资产周转天数', 'DAYS', 'daysTotalAssetsTurnover'), + content: '${indicate.daysTotalAssetsTurnover}', + }, + ], + }, + {type: 'divider'}, + "100日线数据", + { + 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) + + return `
${param.name}
+
+ 开盘: + ${open} +
+
+ 收盘: + ${close} +
+
+ 最低: + ${lowest} +
+
+ 最高: + ${highest} +
` + }, + }, + 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月线数据", + ], + }, + ], }, }, ],