1
0

feat: 使用dialog代替股票详情跳转

This commit is contained in:
2025-10-10 23:13:29 +08:00
parent 846c6fe819
commit 69020852b9
3 changed files with 543 additions and 25 deletions

View File

@@ -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 (
<div className="stock-collection-detail">
@@ -19,19 +18,19 @@ function StockCollectionDetail() {
...crudCommonOptions(),
...paginationTemplate(100, undefined, ['filter-toggler']),
columns: stockListColumns(
navigate,
undefined,
[
{
name: 'score',
label: '得分',
width: 50,
align: 'center',
}
]
},
],
),
}
]
}
},
],
},
)}
</div>
)

View File

@@ -7,10 +7,8 @@ import {
remoteOptions,
stockListColumns,
} from '../../util/amis.tsx'
import {useNavigate} from 'react-router'
function StockList() {
const navigate = useNavigate()
return (
<div className="stock-list">
{amisRender(
@@ -96,7 +94,7 @@ function StockList() {
},
],
},
columns: stockListColumns(navigate),
columns: stockListColumns(),
},
],
},

View File

@@ -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<ColumnSchema> = []) {
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}<br/>${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<ColumnSchema> = []) {
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 `<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>
</div>
<div class="text-center">
<span>收盘:</span>
<span class="font-bold ml-4">${close}</span>
</div>
<div class="text-center">
<span>最低:</span>
<span class="font-bold ml-4">${lowest}</span>
</div>
<div class="text-center">
<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月线数据",
],
},
],
},
},
],