@@ -39,6 +39,7 @@
const statusDict = ref < DictData [ ] > ( [ ] ) ;
const STATUS _COLORS = [ '#3B82F6' , '#10B981' , '#F97316' , '#EC4899' , '#8B5CF6' , '#06B6D4' ] ;
const AMOUNT _LINE _COLOR = '#F59E0B' ;
let pieChartInstance : echarts . ECharts | null = null ;
let barChartInstance : echarts . ECharts | null = null ;
let resizeObserver : ResizeObserver | null = null ;
@@ -48,35 +49,60 @@
router . push ( '/biz/myProjectContract/list' ) ;
}
function getAmountValue ( amount ? : number | string ) {
const value = Number ( amount || 0 ) ;
return Number . isFinite ( value ) ? value : 0 ;
}
function formatAmountYuan ( amount ? : number ) {
return ` ${ Math . round ( getAmountValue ( amount ) ) . toLocaleString ( 'zh-CN' ) } 元 ` ;
}
function formatAmountWan ( amount ? : number , digits = 1 ) {
const wan = getAmountValue ( amount ) / 10000 ;
return Number ( wan . toFixed ( digits ) ) ;
}
const pieChartData = computed ( ( ) => {
if ( ! statusDict . value . length ) {
const fallbackMap = new Map < string , number > ( ) ;
const fallbackMap = new Map < string , { count : number ; amount : number } > ( ) ;
sourceData . value . forEach ( ( item ) => {
const label = item . contractStatus || '未设置' ;
fallbackMap . set ( label , ( fallbackMap . get ( label ) || 0 ) + 1 ) ;
const current = fallbackMap . get ( label ) || { count : 0 , amount : 0 } ;
current . count += 1 ;
current . amount += getAmountValue ( item . contractAmount ) ;
fallbackMap . set ( label , current ) ;
} ) ;
return Array . from ( fallbackMap . entries ( ) ) . map ( ( [ name , value ] , index ) => ( {
name ,
value ,
value : value . count ,
amount : value . amount ,
itemStyle : { color : STATUS _COLORS [ index % STATUS _COLORS . length ] } ,
} ) ) ;
}
const countMap = new Map < string , number > ( ) ;
const amountMap = new Map < string , number > ( ) ;
sourceData . value . forEach ( ( item ) => {
const key = item . contractStatus || 'unknown' ;
countMap . set ( key , ( countMap . get ( key ) || 0 ) + 1 ) ;
amountMap . set ( key , ( amountMap . get ( key ) || 0 ) + getAmountValue ( item . contractAmount ) ) ;
} ) ;
return statusDict . value
. map ( ( item , index ) => ( {
name : item . dictLabelRaw ,
value : countMap . get ( item . dictValue || '' ) || 0 ,
amount : amountMap . get ( item . dictValue || '' ) || 0 ,
itemStyle : { color : STATUS _COLORS [ index % STATUS _COLORS . length ] } ,
} ) )
. filter ( ( item ) => item . value > 0 ) ;
} ) ;
const totalContractAmount = computed ( ( ) => {
return sourceData . value . reduce ( ( sum , item ) => sum + getAmountValue ( item . contractAmount ) , 0 ) ;
} ) ;
const barChartData = computed ( ( ) => {
const statusItems = statusDict . value . length
? statusDict . value . map ( ( item , index ) => ( {
@@ -88,6 +114,7 @@
const monthStatusMap = new Map < string , Map < string , number > > ( ) ;
const monthOrderMap = new Map < string , number > ( ) ;
const monthAmountMap = new Map < string , number > ( ) ;
sourceData . value . forEach ( ( item ) => {
if ( ! item . signDate ) return ;
@@ -101,6 +128,7 @@
}
const monthMap = monthStatusMap . get ( monthLabel ) ! ;
monthMap . set ( statusKey , ( monthMap . get ( statusKey ) || 0 ) + 1 ) ;
monthAmountMap . set ( monthLabel , ( monthAmountMap . get ( monthLabel ) || 0 ) + getAmountValue ( item . contractAmount ) ) ;
} ) ;
const months = Array . from ( monthStatusMap . keys ( ) ) . sort ( ( a , b ) => {
@@ -129,6 +157,7 @@
return {
months ,
totals : totalByMonth ,
amounts : months . map ( ( month ) => monthAmountMap . get ( month ) || 0 ) ,
statusItems : statusItems . length ? statusItems : fallbackSeries ,
monthStatusMap ,
} ;
@@ -240,9 +269,10 @@
textStyle : {
color : isDark ? '#e2e8f0' : '#334155' ,
} ,
formatter : ( { name , value , percent } ) => {
formatter : ( { name , value , percent , data } ) => {
const amount = ( data as { amount ? : number } ) ? . amount || 0 ;
if ( ! total ) return ` ${ name } <br/>数量: 0 ` ;
return ` ${ name } <br/>数量: ${ value } <br/>占比: ${ percent } % ` ;
return ` ${ name } <br/>数量: ${ value } 项<br/>金额: ${ formatAmountYuan ( amount ) } <br/>占比:${ percent } % ` ;
} ,
} ,
legend : {
@@ -281,6 +311,17 @@
fontWeight : 700 ,
} ,
} ,
{
type : 'text' ,
left : 'center' ,
top : '51%' ,
style : {
text : ` 金额 ${ formatAmountWan ( totalContractAmount . value , 2 ) } 万元 ` ,
fill : isDark ? '#94a3b8' : '#64748b' ,
fontSize : 12 ,
fontWeight : 500 ,
} ,
} ,
]
: [ ] ,
} ) ;
@@ -293,10 +334,10 @@
}
const isDark = document . documentElement . getAttribute ( 'data-theme' ) === 'dark' ;
const { months , totals , statusItems , monthStatusMap } = barChartData . value ;
const { months , totals , amounts , statusItems , monthStatusMap } = barChartData . value ;
const hasData = totals . some ( ( item ) => item > 0 ) ;
const categories = months . length ? months : [ ] ;
const series = statusItems . length
const series : echarts . SeriesOption [ ] = statusItems . length
? statusItems . map ( ( status ) => ( {
name : status . label ,
type : 'bar' ,
@@ -353,6 +394,30 @@
data : categories . map ( ( ) => 0 ) ,
z : 10 ,
} ) ;
series . push ( {
name : '合同金额' ,
type : 'line' ,
yAxisIndex : 1 ,
smooth : true ,
symbol : 'circle' ,
symbolSize : 6 ,
itemStyle : {
color : AMOUNT _LINE _COLOR ,
} ,
lineStyle : {
color : AMOUNT _LINE _COLOR ,
width : 2 ,
} ,
emphasis : {
focus : 'series' ,
} ,
tooltip : {
show : false ,
} ,
data : amounts . map ( ( amount ) => formatAmountWan ( amount , 2 ) ) ,
z : 11 ,
} ) ;
}
barChartInstance . setOption ( {
@@ -377,9 +442,15 @@
formatter : ( params ) => {
const dataIndex = params [ 0 ] ? . dataIndex || 0 ;
if ( ! hasData ) return '' ;
const amountItem = params . find ( ( item ) => item . seriesName === '合同金额' ) ;
const lines = params
. filter ( ( item ) => item . seriesName !== '总数' && Number ( item . value ) > 0 )
. filter ( ( item ) => item . seriesName !== '总数' && item . seriesName !== '合同金额' && Number ( item . value ) > 0 )
. map ( ( item ) => ` ${ item . marker } ${ item . seriesName } : ${ item . value } 项 ` ) ;
if ( amounts [ dataIndex ] > 0 ) {
lines . push (
` ${ amountItem ? . marker || ` <span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color: ${ AMOUNT _LINE _COLOR } ;"></span> ` } 合同金额: ${ formatAmountYuan ( amounts [ dataIndex ] ) } ` ,
) ;
}
lines . push ( ` 总数: ${ totals [ dataIndex ] } 项 ` ) ;
return ` ${ categories [ dataIndex ] } <br/> ${ lines . join ( '<br/>' ) } ` ;
} ,
@@ -388,7 +459,7 @@
top : 6 ,
left : 'center' ,
itemGap : 16 ,
data : statusItems . map ( ( item ) => item . label ) ,
data : [ ... statusItems . map ( ( item ) => item . label ) , ... ( hasData ? [ '合同金额' ] : [ ] ) ] ,
textStyle : {
color : isDark ? '#e2e8f0' : '#475569' ,
} ,
@@ -412,23 +483,42 @@
rotate : 30 ,
} ,
} ,
yAxis : {
type : 'value' ,
minInterval : 1 ,
name : '数量' ,
nameTextStyle : {
color : isDark ? '#94a3b8' : '#64748b ',
padding : [ 0 , 0 , 2 , 0 ] ,
} ,
splitL ine : {
lineStyle : {
color : isDark ? 'rgba(71, 85, 105, 0.35)' : 'rgba(203, 213, 225, 0.55)' ,
yAxis : [
{
type : 'value' ,
interval : 1 ,
minInterval : 1 ,
name : '数量 ',
nameTextStyle : {
color : isDark ? '#94a3b8' : '#64748b' ,
padd ing : [ 0 , 0 , 2 , 0 ] ,
} ,
splitLine : {
lineStyle : {
color : isDark ? 'rgba(71, 85, 105, 0.35)' : 'rgba(203, 213, 225, 0.55)' ,
} ,
} ,
axisLabel : {
color : isDark ? '#94a3b8' : '#64748b' ,
} ,
} ,
axisLabel : {
color : isDark ? '#94a3b8' : '#64748b ',
{
type : 'value ',
name : '金额(万元)' ,
min : 0 ,
splitLine : {
show : false ,
} ,
nameTextStyle : {
color : isDark ? '#94a3b8' : '#64748b' ,
padding : [ 0 , 0 , 2 , 0 ] ,
} ,
axisLabel : {
color : isDark ? '#94a3b8' : '#64748b' ,
formatter : ( value ) => ` ${ value } ` ,
} ,
} ,
} ,
] ,
series ,
} ) ;
}