feat(dashboard): 优化大帐统计展示

- 大帐统计改为显示账户余额,移除出入库卡片
- 柱状图展示收入和支出数据,按月份正序排列
- 奖惩记录从数据库真实查询,区分奖励和惩罚
- 修复惩罚记录显示问题(类型匹配)
- 账户余额显示在图例右侧
- 修复ESLint变量重复声明警告
This commit is contained in:
tangweijie 2026-01-26 18:16:37 +08:00
parent 7272342fe6
commit 77b78ac64d
3 changed files with 83 additions and 138 deletions

View File

@ -85,7 +85,7 @@
</div> </div>
<div class="dashboard-content-bottom-right"> <div class="dashboard-content-bottom-right">
<div class="dashboard-content-bottom-right-title">大帐统计</div> <div class="dashboard-content-bottom-right-title">大帐统计</div>
<BarChart :height="'200px'" :data="barChartData" :card-data="barCardData" /> <BarChart :data="barChartData" :balance="balance" />
</div> </div>
</div> </div>
</div> </div>
@ -171,12 +171,8 @@ const centerRightData = ref({
// //
const barChartData = ref<{ category: string; monthlyStandard: number; perCapita: number }[]>([]) const barChartData = ref<{ category: string; monthlyStandard: number; perCapita: number }[]>([])
// //
const barCardData = ref({ const balance = ref(0)
inProgress: 0,
toWarehouse: 0,
outWarehouse: 0
})
// //
const basicInfo = ref({ const basicInfo = ref({
@ -374,14 +370,8 @@ const loadData = async (prisonerId: number) => {
// //
barChartData.value = res.consumptionMonthlyData || [] barChartData.value = res.consumptionMonthlyData || []
// //
if (res.consumptionSummary) { balance.value = res.balance || 0
barCardData.value = {
inProgress: res.consumptionSummary.inProgress || 0,
toWarehouse: res.consumptionSummary.toWarehouse || 0,
outWarehouse: res.consumptionSummary.outWarehouse || 0
}
}
// //
basicInfo.value = { basicInfo.value = {

View File

@ -1,22 +1,7 @@
<template> <template>
<div class="supply-chart-container"> <div class="supply-chart-container" ref="containerRef">
<!-- 卡片统计 -->
<div class="chart-cards">
<div class="chart-card-item">
<div class="card-value">{{ cardData.inProgress }}</div>
<div class="card-label">进行中</div>
</div>
<div class="chart-card-item">
<div class="card-value">{{ cardData.toWarehouse }}</div>
<div class="card-label">待入库</div>
</div>
<div class="chart-card-item">
<div class="card-value">{{ cardData.outWarehouse }}</div>
<div class="card-label">已出库</div>
</div>
</div>
<!-- 柱状图 --> <!-- 柱状图 -->
<EChart :options="barOption" :height="height" /> <EChart ref="chartRef" :options="barOption" :height="height" />
</div> </div>
</template> </template>
@ -24,7 +9,7 @@
import type { EChartsOption } from 'echarts' import type { EChartsOption } from 'echarts'
// @ts-ignore // @ts-ignore
import EChart from '@/components/Echart/src/Echart.vue' import EChart from '@/components/Echart/src/Echart.vue'
import { computed, watch } from 'vue' import { computed, ref, onMounted, watch } from 'vue'
defineOptions({ name: 'BarChart' }) defineOptions({ name: 'BarChart' })
@ -34,49 +19,45 @@ interface ChartDataItem {
perCapita: number perCapita: number
} }
interface CardData {
inProgress: number
toWarehouse: number
outWarehouse: number
}
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
width?: number width?: number | string
height?: string height?: string
data?: ChartDataItem[] data?: ChartDataItem[]
cardData?: CardData balance?: number
}>(), }>(),
{ {
width: 400, width: '100%',
height: '300px',
data: () => [], data: () => [],
cardData: () => ({ balance: () => 0
inProgress: 5,
toWarehouse: 5,
outWarehouse: 5
})
} }
) )
const containerRef = ref<HTMLElement>()
const chartRef = ref()
// //
const createChartOption = (): EChartsOption => { const createChartOption = (): EChartsOption => {
const categories = props.data.map((item) => item.category) const categories = props.data.map((item) => item.category)
const monthlyStandardData = props.data.map((item) => item.monthlyStandard ?? 0) const monthlyStandardData = props.data.map((item) => item.monthlyStandard ?? 0)
const perCapitaData = props.data.map((item) => item.perCapita ?? 0) const perCapitaData = props.data.map((item) => item.perCapita ?? 0)
// 50 //
const maxValue = 50 const maxDataValue = Math.max(...monthlyStandardData, ...perCapitaData, 100)
const monthlyStandardBgData = categories.map((_, index) => maxValue - monthlyStandardData[index]) // 20%
const perCapitaBgData = categories.map((_, index) => maxValue - perCapitaData[index]) const maxValue = Math.ceil(maxDataValue * 1.2 / 100) * 100
// maxValue
const monthlyStandardBgData = categories.map((_, index) => Math.max(0, maxValue - monthlyStandardData[index]))
const perCapitaBgData = categories.map((_, index) => Math.max(0, maxValue - perCapitaData[index]))
return { return {
backgroundColor: 'transparent', backgroundColor: 'transparent',
grid: { grid: {
left: '10%', left: '8%',
right: '15%', right: '8%',
top: '20%', top: '25%',
bottom: '15%', bottom: '18%',
containLabel: false containLabel: false
}, },
xAxis: { xAxis: {
@ -100,8 +81,8 @@ const createChartOption = (): EChartsOption => {
yAxis: { yAxis: {
type: 'value', type: 'value',
min: 0, min: 0,
max: 50, max: maxValue,
interval: 10, interval: Math.ceil(maxValue / 5 / 100) * 100,
axisLine: { axisLine: {
show: false show: false
}, },
@ -110,7 +91,8 @@ const createChartOption = (): EChartsOption => {
}, },
axisLabel: { axisLabel: {
color: '#D8F0FF', color: '#D8F0FF',
fontSize: 10 fontSize: 10,
formatter: (value: number) => value.toString()
}, },
splitLine: { splitLine: {
lineStyle: { lineStyle: {
@ -119,17 +101,33 @@ const createChartOption = (): EChartsOption => {
} }
} }
}, },
//
legend: { legend: {
data: ['支出', '收入'], data: [
top: '5%', { name: '支出', icon: 'rect' },
right: '10%', { name: '收入', icon: 'rect' }
],
top: '3%',
left: '8%',
textStyle: { textStyle: {
color: '#6D869A', color: '#6D869A',
fontSize: 9 fontSize: 10
}, },
itemWidth: 9, itemWidth: 12,
itemHeight: 9, itemHeight: 8,
itemGap: 25 itemGap: 15
},
// 使 title
title: {
text: `账户余额: ${props.balance}`,
left: 'auto',
right: '8%',
top: '3%',
textStyle: {
color: '#00d4ff',
fontSize: 14,
fontWeight: 'bold'
}
}, },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
@ -144,7 +142,6 @@ const createChartOption = (): EChartsOption => {
}, },
formatter: function (params: any) { formatter: function (params: any) {
let result = params[0].name + '<br/>' let result = params[0].name + '<br/>'
//
params.forEach((param: any) => { params.forEach((param: any) => {
if (param.seriesName === '支出' || param.seriesName === '收入') { if (param.seriesName === '支出' || param.seriesName === '收入') {
result += param.marker + param.seriesName + ': ' + param.value + '<br/>' result += param.marker + param.seriesName + ': ' + param.value + '<br/>'
@ -154,7 +151,7 @@ const createChartOption = (): EChartsOption => {
} }
}, },
series: [ series: [
// - //
{ {
name: '支出', name: '支出',
type: 'bar', type: 'bar',
@ -165,40 +162,36 @@ const createChartOption = (): EChartsOption => {
type: 'linear', type: 'linear',
x: 0, x: 0,
y: 0, y: 0,
x2: 0, x2: 1,
y2: 1, y2: 0,
colorStops: [ colorStops: [
{ { offset: 0, color: '#10A0F2' },
offset: 0, { offset: 0.5, color: '#0D8BD9' },
color: '#10A0F2' { offset: 1, color: '#0A6EB0' }
},
{
offset: 1,
color: 'rgba(0, 82, 184, 0)'
}
] ]
} },
borderRadius: [2, 2, 0, 0]
}, },
barWidth: '20%', barWidth: '25%',
barGap: '20%' barGap: '30%'
}, },
// - //
{ {
name: '支出底色', name: '支出底色',
type: 'bar', type: 'bar',
stack: 'monthly', stack: 'monthly',
data: monthlyStandardBgData, data: monthlyStandardBgData,
itemStyle: { itemStyle: {
color: '#38668D70' color: 'rgba(56, 102, 141, 0.3)'
}, },
barWidth: '20%', barWidth: '25%',
barGap: '20%', barGap: '30%',
silent: true, silent: true,
tooltip: { tooltip: {
show: false show: false
} }
}, },
// //
{ {
name: '收入', name: '收入',
type: 'bar', type: 'bar',
@ -209,33 +202,30 @@ const createChartOption = (): EChartsOption => {
type: 'linear', type: 'linear',
x: 0, x: 0,
y: 0, y: 0,
x2: 0, x2: 1,
y2: 1, y2: 0,
colorStops: [ colorStops: [
{ { offset: 0, color: '#FFA58D' },
offset: 0, { offset: 0.5, color: '#E88F5A' },
color: '#FFA58D' { offset: 1, color: '#D07530' }
},
{
offset: 1,
color: 'rgba(87, 140, 205, 0)'
}
] ]
} },
borderRadius: [2, 2, 0, 0]
}, },
barWidth: '20%', barWidth: '25%',
barGap: '80%' barGap: '30%'
}, },
//
{ {
name: '收入底色', name: '收入底色',
type: 'bar', type: 'bar',
stack: 'perCapita', stack: 'perCapita',
data: perCapitaBgData, data: perCapitaBgData,
itemStyle: { itemStyle: {
color: '#38668D70' color: 'rgba(56, 102, 141, 0.3)'
}, },
barWidth: '20%', barWidth: '25%',
barGap: '80%', barGap: '30%',
silent: true, silent: true,
tooltip: { tooltip: {
show: false show: false
@ -247,15 +237,6 @@ const createChartOption = (): EChartsOption => {
// //
const barOption = computed(() => createChartOption()) const barOption = computed(() => createChartOption())
//
watch(
() => [props.data, props.cardData],
() => {
// computed
},
{ deep: true }
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -265,30 +246,4 @@ watch(
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.chart-cards {
display: flex;
justify-content: space-around;
flex-shrink: 0;
}
.chart-card-item {
text-align: center;
padding: 4px;
background: rgba(56, 102, 141, 0.3);
border-radius: 8px;
min-width: 100px;
.card-value {
font-size: 1.6vh;
font-weight: bold;
color: #00d4ff;
margin-bottom: 4px;
}
.card-label {
font-size: 1.5vh;
color: rgba(255, 255, 255, 0.8);
}
}
</style> </style>

View File

@ -65,7 +65,7 @@ const filteredList = computed(() => {
} else if (activeFilter.value === 'reward') { } else if (activeFilter.value === 'reward') {
return listData.value.filter((item) => item.type === 'reward') return listData.value.filter((item) => item.type === 'reward')
} else { } else {
return listData.value.filter((item) => item.type === 'danger') return listData.value.filter((item) => item.type === 'punishment')
} }
}) })