@@ -0,0 +1,405 @@
import { useState , useEffect , useMemo } from 'react'
import { useQuery } from '@tanstack/react-query'
import { BarChart , Bar , LineChart , Line , XAxis , YAxis , CartesianGrid , Tooltip , ResponsiveContainer , Cell , PieChart , Pie , Legend } from 'recharts'
import api from '@/lib/api'
import { MetricCard } from '@/components/MetricCard'
import { LoadingSpinner } from '@/components/LoadingSpinner'
import { CollapsibleSection } from '@/components/CollapsibleSection'
import { formatNumber , formatCurrency , formatPercent } from '@/lib/utils'
import { Landmark , Settings2 , RotateCcw } from 'lucide-react'
// ============ 评分规则类型 ============
interface ScoringRule {
weight : number
excellent : number
pass : number
higherIsBetter : boolean
}
interface ScoringConfig {
netMargin : ScoringRule
stability : ScoringRule
storeHealth : ScoringRule
scale : ScoringRule
discountRate : ScoringRule
memberShare : ScoringRule
}
const DEFAULT_CONFIG : ScoringConfig = {
netMargin : { weight : 25 , excellent : 10 , pass : 5 , higherIsBetter : true } ,
stability : { weight : 20 , excellent : 0.15 , pass : 0.3 , higherIsBetter : false } ,
storeHealth : { weight : 20 , excellent : 10 , pass : 25 , higherIsBetter : false } ,
scale : { weight : 15 , excellent : 500000 , pass : 200000 , higherIsBetter : true } ,
discountRate : { weight : 10 , excellent : 18 , pass : 25 , higherIsBetter : false } ,
memberShare : { weight : 10 , excellent : 15 , pass : 5 , higherIsBetter : true } ,
}
const CHANNEL_LABELS : Record < string , string > = {
cash : '现金' , alipay : '支付宝' , wechat : '微信' , meituan : '美团堂食' , unionpay : '银联' ,
douyin : '抖音' , credit : '挂账' , jd_delivery : '京东外卖' , meituan_delivery : '美团外卖' , taobao_delivery : '淘宝外卖' ,
}
const CHANNEL_COLORS : Record < string , string > = {
wechat : '#07c160' , alipay : '#1677ff' , meituan_delivery : '#ffd700' , meituan : '#ff6b35' ,
taobao_delivery : '#ff4400' , douyin : '#000000' , cash : '#52c41a' , jd_delivery : '#e1251b' ,
unionpay : '#f5222d' , credit : '#722ed1' ,
}
const RISK_COLORS : Record < string , string > = { '红色' : '#ef4444' , '黄色' : '#eab308' , '绿色' : '#22c55e' }
function loadConfig ( ) : ScoringConfig {
try {
const saved = localStorage . getItem ( 'bank-scoring-config' )
if ( saved ) return { . . . DEFAULT_CONFIG , . . . JSON . parse ( saved ) }
} catch { }
return DEFAULT_CONFIG
}
function saveConfig ( config : ScoringConfig ) {
localStorage . setItem ( 'bank-scoring-config' , JSON . stringify ( config ) )
}
// 计算单项得分 (0-100)
function scoreItem ( value : number , rule : ScoringRule ) : number {
if ( rule . higherIsBetter ) {
if ( value >= rule . excellent ) return 100
if ( value <= rule . pass ) return 0
return Math . round ( ( value - rule . pass ) / ( rule . excellent - rule . pass ) * 100 )
} else {
if ( value <= rule . excellent ) return 100
if ( value >= rule . pass ) return 0
return Math . round ( ( rule . pass - value ) / ( rule . pass - rule . excellent ) * 100 )
}
}
// 计算信用等级
function getGrade ( totalScore : number ) : { grade : string ; color : string ; label : string } {
if ( totalScore >= 85 ) return { grade : 'A' , color : '#22c55e' , label : '优秀 - 建议授信' }
if ( totalScore >= 70 ) return { grade : 'B' , color : '#3b82f6' , label : '良好 - 可授信' }
if ( totalScore >= 50 ) return { grade : 'C' , color : '#eab308' , label : '一般 - 谨慎授信' }
return { grade : 'D' , color : '#ef4444' , label : '风险 - 不建议授信' }
}
export function BankPage() {
const [ config , setConfig ] = useState < ScoringConfig > ( DEFAULT_CONFIG )
const [ showConfig , setShowConfig ] = useState ( false )
useEffect ( ( ) = > { setConfig ( loadConfig ( ) ) } , [ ] )
const { data : reportData , isLoading } = useQuery ( {
queryKey : [ 'bank/report' ] ,
queryFn : ( ) = > api . get ( '/bank/report' ) ,
} )
const d = ( reportData as any ) ? . data
const ov = d ? . overview
const daily = d ? . daily || [ ]
const wf = d ? . waterfall
const risk = d ? . risk || [ ]
const channel = d ? . channel || { }
const stores = d ? . stores || [ ]
const stability = d ? . stability
// 计算各项指标值
const metrics = useMemo ( ( ) = > {
if ( ! ov || ! wf || ! stability ) return null
const netMargin = wf . received > 0 ? ( wf . net_profit / wf . received * 100 ) : 0
const cv = stability . cv
const redStores = risk . filter ( ( r : any ) = > r . risk_level === '红色' ) . reduce ( ( s : number , r : any ) = > s + r . store_count , 0 )
const totalStores = risk . reduce ( ( s : number , r : any ) = > s + r . store_count , 0 )
const redPct = totalStores > 0 ? ( redStores / totalStores * 100 ) : 0
const scale = ov . received
const discountRate = ov . discount_rate_pct
const memberShare = ov . member_share_pct
return { netMargin , cv , redPct , scale , discountRate , memberShare }
} , [ ov , wf , stability , risk ] )
// 计算评分
const scoring = useMemo ( ( ) = > {
if ( ! metrics ) return null
const items = [
{ key : 'netMargin' , label : '净利率' , value : metrics.netMargin , unit : '%' , rule : config.netMargin , score : scoreItem ( metrics . netMargin , config . netMargin ) } ,
{ key : 'stability' , label : '营收稳定性' , value : metrics.cv , unit : '' , rule : config.stability , score : scoreItem ( metrics . cv , config . stability ) } ,
{ key : 'storeHealth' , label : '门店健康度' , value : metrics.redPct , unit : '%' , rule : config.storeHealth , score : scoreItem ( metrics . redPct , config . storeHealth ) } ,
{ key : 'scale' , label : '规模体量' , value : metrics.scale , unit : '元' , rule : config.scale , score : scoreItem ( metrics . scale , config . scale ) } ,
{ key : 'discountRate' , label : '优惠率' , value : metrics.discountRate , unit : '%' , rule : config.discountRate , score : scoreItem ( metrics . discountRate , config . discountRate ) } ,
{ key : 'memberShare' , label : '会员占比' , value : metrics.memberShare , unit : '%' , rule : config.memberShare , score : scoreItem ( metrics . memberShare , config . memberShare ) } ,
]
const totalWeight = items . reduce ( ( s , i ) = > s + i . rule . weight , 0 )
const totalScore = totalWeight > 0 ? Math . round ( items . reduce ( ( s , i ) = > s + i . score * i . rule . weight , 0 ) / totalWeight ) : 0
return { items , totalScore , totalWeight , grade : getGrade ( totalScore ) }
} , [ metrics , config ] )
// 利润瀑布数据
const waterfallChart = useMemo ( ( ) = > {
if ( ! wf ) return [ ]
return [
{ name : '实收' , value : wf.received , base : 0 , fill : '#3b82f6' } ,
{ name : '食材' , value : - wf . food_cost , base : wf.received , fill : '#f97316' } ,
{ name : '人工' , value : - wf . wage , base : wf.received - wf . food_cost , fill : '#3b82f6' } ,
{ name : '房租' , value : - wf . rent , base : wf.received - wf . food_cost - wf . wage , fill : '#8b5cf6' } ,
{ name : '水电' , value : - wf . utility , base : wf.received - wf . food_cost - wf . wage - wf . rent , fill : '#06b6d4' } ,
{ name : '其他' , value : - ( wf . dorm + wf . commission + wf . other_expense ) , base : wf.received - wf . food_cost - wf . wage - wf . rent - wf . utility , fill : '#64748b' } ,
{ name : '净利润' , value : wf.net_profit , base : 0 , fill : wf.net_profit >= 0 ? '#22c55e' : '#ef4444' } ,
]
} , [ wf ] )
// 渠道饼图
const channelPie = useMemo ( ( ) = > {
return Object . entries ( channel ) . map ( ( [ k , v ] ) = > ( { name : k , value : v as number } ) ) . sort ( ( a , b ) = > b . value - a . value )
} , [ channel ] )
// 风险饼图
const riskPie = useMemo ( ( ) = > {
return risk . map ( ( r : any ) = > ( { name : r.risk_level , value : r.store_count , fill : RISK_COLORS [ r . risk_level ] || '#999' } ) )
} , [ risk ] )
// 日度趋势
const dailyChart = useMemo ( ( ) = > {
return daily . map ( ( r : any ) = > ( { date : r.business_date?.substring ( 5 , 10 ) , received : r.received , bills : r.bill_count } ) )
} , [ daily ] )
const top10 = stores . slice ( 0 , 10 )
const totalChannel = channelPie . reduce ( ( s , c ) = > s + c . value , 0 )
if ( isLoading ) return < LoadingSpinner text = "加载银行授信数据..." / >
const updateRule = ( key : keyof ScoringConfig , field : keyof ScoringRule , value : number ) = > {
const newConfig = { . . . config , [ key ] : { . . . config [ key ] , [ field ] : value } }
setConfig ( newConfig )
saveConfig ( newConfig )
}
const resetConfig = ( ) = > {
setConfig ( DEFAULT_CONFIG )
saveConfig ( DEFAULT_CONFIG )
}
return (
< div className = "space-y-4" >
{ /* 标题 */ }
< div className = "flex items-center justify-between" >
< div >
< h1 className = "flex items-center gap-2 text-xl font-bold" > < Landmark size = { 20 } / > 银 行 授 信 数 据 报 告 < / h1 >
< p className = "mt-0.5 text-xs text-muted-foreground" > 企 业 经 营 数 据 · 信 用 评 估 · 2026 年 4 月 < / p >
< / div >
< button onClick = { ( ) = > setShowConfig ( ! showConfig ) } className = "flex items-center gap-1 rounded-md border px-3 py-1.5 text-xs hover:bg-accent" >
< Settings2 size = { 14 } / > 评 分 规 则
< / button >
< / div >
{ /* 评分规则配置面板 */ }
{ showConfig && (
< CollapsibleSection title = "评分规则配置" subtitle = "调整各指标权重和阈值,实时重新计算信用等级" defaultOpen = { true } >
< div className = "space-y-3" >
< div className = "grid gap-3 md:grid-cols-3 lg:grid-cols-6" >
{ ( Object . keys ( config ) as ( keyof ScoringConfig ) [ ] ) . map ( key = > {
const rule = config [ key ]
const labels : Record < string , string > = { netMargin : '净利率(%)' , stability : '营收稳定性(CV)' , storeHealth : '门店健康度(红色占比%)' , scale : '规模体量(元)' , discountRate : '优惠率(%)' , memberShare : '会员占比(%)' }
return (
< div key = { key } className = "rounded-lg border p-3 space-y-2" >
< p className = "text-xs font-bold" > { labels [ key ] } < / p >
< div >
< label className = "text-[10px] text-muted-foreground" > 权 重 ( % ) < / label >
< input type = "number" value = { rule . weight } onChange = { e = > updateRule ( key , 'weight' , parseFloat ( e . target . value ) || 0 ) }
className = "w-full rounded border px-2 py-1 text-xs" / >
< / div >
< div >
< label className = "text-[10px] text-muted-foreground" > { rule . higherIsBetter ? '优秀阈值' : '优秀阈值(≤)' } < / label >
< input type = "number" step = "0.01" value = { rule . excellent } onChange = { e = > updateRule ( key , 'excellent' , parseFloat ( e . target . value ) || 0 ) }
className = "w-full rounded border px-2 py-1 text-xs" / >
< / div >
< div >
< label className = "text-[10px] text-muted-foreground" > { rule . higherIsBetter ? '及格阈值(≥)' : '及格阈值(≥)' } < / label >
< input type = "number" step = "0.01" value = { rule . pass } onChange = { e = > updateRule ( key , 'pass' , parseFloat ( e . target . value ) || 0 ) }
className = "w-full rounded border px-2 py-1 text-xs" / >
< / div >
< / div >
)
} ) }
< / div >
< div className = "flex items-center gap-4" >
< span className = "text-xs" > 权 重 总 和 : < strong className = { scoring ? . totalWeight === 100 ? 'text-green-600' : 'text-red-600' } > { scoring ? . totalWeight || 0 } % < / strong > < / span >
< button onClick = { resetConfig } className = "flex items-center gap-1 rounded-md border px-2 py-1 text-xs hover:bg-accent" >
< RotateCcw size = { 12 } / > 重 置 默 认
< / button >
< / div >
< / div >
< / CollapsibleSection >
) }
{ /* ① 企业经营概况 */ }
< CollapsibleSection title = "企业经营概况" subtitle = "月度核心指标与日度营收趋势" >
< div className = "grid grid-cols-2 gap-3 md:grid-cols-4" >
< MetricCard title = "月度实收" value = { ov ? . received } format = "currency" description = { ` ${ formatNumber ( ov ? . bill_count ) } 笔账单 ` } / >
< MetricCard title = "客单价" value = { ov ? . avg_bill_value } format = "currency" description = "实收 ÷ 账单数" / >
< MetricCard title = "理论毛利率" value = { ov ? . theoretical_margin_pct } format = "percent" description = "菜品定价毛利空间" / >
< MetricCard title = "门店数量" value = { stores . length } unit = "家" description = { ` 活跃经营门店总数 ` } / >
< / div >
< div className = "mt-4" >
< ResponsiveContainer width = "100%" height = { 220 } >
< LineChart data = { dailyChart } margin = { { top : 10 , right : 10 , left : 0 , bottom : 0 } } >
< CartesianGrid strokeDasharray = "3 3" / >
< XAxis dataKey = "date" tick = { { fontSize : 10 } } / >
< YAxis tickFormatter = { ( v ) = > v >= 10000 ? ` ${ ( v / 10000 ) . toFixed ( 0 ) } 万 ` : v } tick = { { fontSize : 10 } } / >
< Tooltip formatter = { ( v : any ) = > [ formatCurrency ( v ) , '日实收' ] } / >
< Line type = "monotone" dataKey = "received" stroke = "#3b82f6" name = "日实收" dot = { false } strokeWidth = { 2 } / >
< / LineChart >
< / ResponsiveContainer >
< / div >
< / CollapsibleSection >
{ /* ② 盈利能力分析 */ }
< CollapsibleSection title = "盈利能力分析" subtitle = "利润瀑布 · 净利率 · 成本费用结构" >
{ wf && (
< >
< div className = "grid grid-cols-2 gap-3 md:grid-cols-4" >
< MetricCard title = "净利润" value = { wf . net_profit } format = "currency" status = { wf . net_profit > 0 ? 'good' : 'bad' } description = { ` 净利率 ${ wf . received > 0 ? formatPercent ( wf . net_profit / wf . received * 100 ) : '-' } ` } / >
< MetricCard title = "食材成本率" value = { wf . received > 0 ? wf . food_cost / wf . received * 100 : 0 } format = "percent" status = { wf . received > 0 && wf . food_cost / wf . received * 100 > 35 ? 'warn' : 'good' } description = "食材成本 ÷ 实收" / >
< MetricCard title = "费用率" value = { wf . received > 0 ? wf . total_expense / wf . received * 100 : 0 } format = "percent" status = { wf . received > 0 && wf . total_expense / wf . received * 100 > 45 ? 'warn' : 'good' } description = "经营费用 ÷ 实收" / >
< MetricCard title = "实收分配" value = { wf . received > 0 ? wf . net_profit / wf . received * 100 : 0 } format = "percent" description = "净利润占实收比例" / >
< / div >
< div className = "mt-4" >
< ResponsiveContainer width = "100%" height = { 250 } >
< BarChart data = { waterfallChart } margin = { { top : 10 , right : 10 , left : 0 , bottom : 0 } } >
< CartesianGrid strokeDasharray = "3 3" / >
< XAxis dataKey = "name" tick = { { fontSize : 10 } } angle = { - 20 } textAnchor = "middle" height = { 60 } / >
< YAxis tickFormatter = { ( v ) = > v >= 10000 ? ` ${ ( v / 10000 ) . toFixed ( 0 ) } 万 ` : v } tick = { { fontSize : 10 } } / >
< Tooltip formatter = { ( v : any ) = > [ formatCurrency ( v ) , '金额' ] } / >
< Bar dataKey = "value" radius = { [ 4 , 4 , 0 , 0 ] } shape = { ( props : any ) = > {
const { x , y , width , height , payload , fill } = props
if ( ! payload . value || payload . value === 0 ) return < rect / >
const scale = height / payload . value
const newY = y - payload . base * scale
return < rect x = { x } y = { newY } width = { width } height = { height } fill = { fill } rx = { 4 } ry = { 4 } / >
} } >
{ waterfallChart . map ( ( entry , i ) = > < Cell key = { i } fill = { entry . fill } / > ) }
< / Bar >
< / BarChart >
< / ResponsiveContainer >
< / div >
< / >
) }
< / CollapsibleSection >
{ /* ③ 门店资产质量 */ }
< CollapsibleSection title = "门店资产质量" subtitle = "风险等级分布 · 营收TOP10" >
< div className = "grid gap-4 lg:grid-cols-2" >
< div >
< ResponsiveContainer width = "100%" height = { 220 } >
< PieChart >
< Pie data = { riskPie } dataKey = "value" nameKey = "name" cx = "50%" cy = "50%" outerRadius = { 80 } label = { ( e : any ) = > ` ${ e . name } : ${ e . value } 家 ` } >
{ riskPie . map ( ( entry : any , i : number ) = > < Cell key = { i } fill = { entry . fill } / > ) }
< / Pie >
< Tooltip formatter = { ( v : any , name : any ) = > [ ` ${ v } 家 ` , name ] } / >
< Legend wrapperStyle = { { fontSize : 10 } } / >
< / PieChart >
< / ResponsiveContainer >
< / div >
< div >
< ResponsiveContainer width = "100%" height = { 220 } >
< BarChart data = { top10 } layout = "vertical" margin = { { top : 5 , right : 10 , left : 80 , bottom : 0 } } >
< CartesianGrid strokeDasharray = "3 3" / >
< XAxis type = "number" tickFormatter = { ( v ) = > v >= 10000 ? ` ${ ( v / 10000 ) . toFixed ( 0 ) } 万 ` : v } tick = { { fontSize : 10 } } / >
< YAxis type = "category" dataKey = "store_name" tick = { { fontSize : 9 } } width = { 80 } / >
< Tooltip formatter = { ( v : any ) = > [ formatCurrency ( v ) , '实收' ] } / >
< Bar dataKey = "received" fill = "#3b82f6" radius = { [ 0 , 4 , 4 , 0 ] } / >
< / BarChart >
< / ResponsiveContainer >
< / div >
< / div >
< / CollapsibleSection >
{ /* ④ 经营稳定性指标 */ }
< CollapsibleSection title = "经营稳定性指标" subtitle = "营收波动率 · 优惠率 · 会员占比 · 渠道结构" >
< div className = "grid grid-cols-2 gap-3 md:grid-cols-4" >
< MetricCard title = "日度营收波动率" value = { stability ? stability . cv * 100 : 0 } format = "percent" status = { stability && stability . cv < 0.2 ? 'good' : stability && stability . cv < 0.3 ? 'warn' : 'bad' } description = { ` 变异系数CV · 日均 ${ formatCurrency ( stability ? . mean_daily_revenue ) } ` } / >
< MetricCard title = "优惠率" value = { ov ? . discount_rate_pct } format = "percent" status = { Number ( ov ? . discount_rate_pct ) > 25 ? 'bad' : Number ( ov ? . discount_rate_pct ) > 18 ? 'warn' : 'good' } description = "优惠额 ÷ (实收+优惠额)" / >
< MetricCard title = "会员消费占比" value = { ov ? . member_share_pct } format = "percent" status = { Number ( ov ? . member_share_pct ) > 15 ? 'good' : 'warn' } description = "会员账单 ÷ 总账单" / >
< MetricCard title = "活跃渠道数" value = { channelPie . length } unit = "个" description = "有交易记录的支付渠道" / >
< / div >
{ channelPie . length > 0 && (
< div className = "mt-4" >
< ResponsiveContainer width = "100%" height = { 200 } >
< PieChart >
< Pie data = { channelPie } dataKey = "value" nameKey = "name" cx = "50%" cy = "50%" outerRadius = { 70 } label = { ( e : any ) = > ` ${ CHANNEL_LABELS [ e . name ] || e . name } ${ totalChannel > 0 ? ( e . value / totalChannel * 100 ) . toFixed ( 1 ) : 0 } % ` } >
{ channelPie . map ( ( entry : any , i : number ) = > < Cell key = { i } fill = { CHANNEL_COLORS [ entry . name ] || '#999' } / > ) }
< / Pie >
< Tooltip formatter = { ( v : any , name : any ) = > [ formatCurrency ( v ) , CHANNEL_LABELS [ name ] || name ] } / >
< Legend wrapperStyle = { { fontSize : 10 } } formatter = { ( v : any ) = > CHANNEL_LABELS [ v ] || v } / >
< / PieChart >
< / ResponsiveContainer >
< / div >
) }
< / CollapsibleSection >
{ /* ⑤ 银行授信评估摘要 */ }
< CollapsibleSection title = "银行授信评估摘要" subtitle = "基于可配置评分规则的信用等级评估" >
{ scoring && (
< >
{ /* 信用等级总览 */ }
< div className = "flex items-center gap-6 rounded-lg border p-4" style = { { borderColor : scoring.grade.color + '40' } } >
< div className = "flex h-20 w-20 items-center justify-center rounded-full text-4xl font-bold" style = { { backgroundColor : scoring.grade.color + '20' , color : scoring.grade.color } } >
{ scoring . grade . grade }
< / div >
< div className = "flex-1" >
< p className = "text-lg font-bold" style = { { color : scoring.grade.color } } > { scoring . grade . label } < / p >
< p className = "mt-1 text-2xl font-bold" > { scoring . totalScore } < span className = "text-sm text-muted-foreground" > / 100 分 < / span > < / p >
< p className = "text-xs text-muted-foreground" > 综 合 信 用 评 分 · 权 重 总 和 { scoring . totalWeight } % < / p >
< / div >
< / div >
{ /* 各指标得分明细 */ }
< div className = "mt-4 overflow-x-auto" >
< table className = "w-full text-xs" >
< thead >
< tr className = "border-b text-left text-muted-foreground" >
< th className = "pb-2 pr-4" > 指 标 < / th >
< th className = "pb-2 pr-4" > 实 际 值 < / th >
< th className = "pb-2 pr-4" > 优 秀 阈 值 < / th >
< th className = "pb-2 pr-4" > 及 格 阈 值 < / th >
< th className = "pb-2 pr-4" > 权 重 < / th >
< th className = "pb-2 pr-4" > 得 分 < / th >
< th className = "pb-2" > 加 权 得 分 < / th >
< / tr >
< / thead >
< tbody >
{ scoring . items . map ( item = > (
< tr key = { item . key } className = "border-b" >
< td className = "py-2 pr-4 font-medium" > { item . label } < / td >
< td className = "py-2 pr-4" > { item . unit === '元' ? formatCurrency ( item . value ) : ` ${ item . value . toFixed ( 2 ) } ${ item . unit } ` } < / td >
< td className = "py-2 pr-4" > { item . unit === '元' ? formatCurrency ( item . rule . excellent ) : ` ${ item . rule . excellent } ${ item . unit } ` } < / td >
< td className = "py-2 pr-4" > { item . unit === '元' ? formatCurrency ( item . rule . pass ) : ` ${ item . rule . pass } ${ item . unit } ` } < / td >
< td className = "py-2 pr-4" > { item . rule . weight } % < / td >
< td className = "py-2 pr-4" >
< span className = "font-bold" style = { { color : item.score >= 80 ? '#22c55e' : item . score >= 50 ? '#eab308' : '#ef4444' } } > { item . score } < / span >
< / td >
< td className = "py-2" > { ( item . score * item . rule . weight / 100 ) . toFixed ( 1 ) } < / td >
< / tr >
) ) }
< / tbody >
< / table >
< / div >
{ /* 风险提示 */ }
< div className = "mt-4 space-y-2" >
< p className = "text-xs font-bold" > 风 险 提 示 < / p >
{ scoring . items . filter ( i = > i . score < 60 ) . map ( i = > (
< div key = { i . key } className = "flex items-center gap-2 rounded border border-yellow-200 bg-yellow-50/40 p-2 text-xs" >
< span className = "text-yellow-600" > ⚠ < / span >
< span > < strong > { i . label } < / strong > : 得 分 { i . score } , 实 际 值 { i . unit === '元' ? formatCurrency ( i . value ) : ` ${ i . value . toFixed ( 2 ) } ${ i . unit } ` } , { i . rule . higherIsBetter ? ` 建议提升至 ${ i . unit === '元' ? formatCurrency ( i . rule . excellent ) : i . rule . excellent + i . unit } 以上 ` : ` 建议控制在 ${ i . rule . excellent } ${ i . unit } 以内 ` } < / span >
< / div >
) ) }
{ scoring . items . filter ( i = > i . score < 60 ) . length === 0 && (
< div className = "flex items-center gap-2 rounded border border-green-200 bg-green-50/40 p-2 text-xs" >
< span className = "text-green-600" > ✓ < / span >
< span > 各 项 指 标 均 达 标 , 经 营 状 况 良 好 < / span >
< / div >
) }
< / div >
< / >
) }
< / CollapsibleSection >
< / div >
)
}