2026-01-21 16:17:14 +08:00

647 lines
16 KiB
Vue

<template>
<div class="dashboard-container">
<div class="prison-name">{{ prisonerName }}</div>
<div class="current-time">{{ currentTime }}</div>
<div class="dashboard-content">
<div class="dashboard-content-top">
<div class="dashboard-content-top-left">
<InfoCard :basic-info="basicInfo" :interview-records="interviewRecords" />
</div>
<div class="dashboard-content-top-center">
<div class="gauge-container">
<div class="dashboard-content-top-center-data">
<!-- 左侧第一个卡片 -->
<div class="info-card-item">
<div class="card-number">{{ centerLeftData.top.value }}</div>
<div class="card-label">{{ centerLeftData.top.label }}</div>
</div>
<!-- 左侧第二个卡片 -->
<div class="card-row">
<div class="info-card-item">
<div class="card-number">{{ centerLeftData.middle.left.value }}</div>
<div class="card-label">{{ centerLeftData.middle.left.label }}</div>
</div>
<div class="info-card-item">
<div class="card-number">{{ centerLeftData.middle.right.value }}</div>
<div class="card-label">{{ centerLeftData.middle.right.label }}</div>
</div>
</div>
</div>
<div class="dashboard-content-top-center-center">
<GaugeChart :height="'240px'" :value="gaugeValue" :name="gaugeName" />
</div>
<div class="dashboard-content-top-center-data">
<!-- 右侧第一个卡片 -->
<div class="info-card-item">
<div class="card-number">{{ centerRightData.top.value }}</div>
<div class="card-label">{{ centerRightData.top.label }}</div>
</div>
<!-- 右侧第二个卡片 -->
<div class="card-row">
<div class="info-card-item">
<div class="card-number">{{ centerRightData.middle.left.value }}</div>
<div class="card-label">{{ centerRightData.middle.left.label }}</div>
</div>
<div class="info-card-item">
<div class="card-number">{{ centerRightData.middle.right.value }}</div>
<div class="card-label">{{ centerRightData.middle.right.label }}</div>
</div>
</div>
</div>
</div>
<div class="list-container">
<div class="list-card-item">
<div class="list-card-item-icon icon-location"></div>
<div class="list-card-item-value">108</div>
</div>
<div class="list-card-item">
<div class="list-card-item-icon icon-person"></div>
<div class="list-card-item-value">108</div>
</div>
<div class="list-card-item">
<div class="list-card-item-icon icon-person2"></div>
<div class="list-card-item-value">108</div>
</div>
<div class="list-card-item">
<div class="list-card-item-icon icon-car"></div>
<div class="list-card-item-value">108</div>
</div>
</div>
</div>
<div class="dashboard-content-top-right">
<ScoreAssessment :data="scoreRecords" />
</div>
</div>
<div class="dashboard-content-bottom">
<div class="dashboard-content-bottom-left">
<ConsumptionRecords
:data="consumptionRecords"
:relat-data="relationships"
:remit-data="remittanceRecords"
/>
</div>
<div class="dashboard-content-bottom-center">
<RecentRewardsPunishments :data="rewardsPunishments" />
</div>
<div class="dashboard-content-bottom-right">
<div class="dashboard-content-bottom-right-title">大帐统计</div>
<BarChart :height="'200px'" :data="barChartData" :card-data="barCardData" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
// @ts-ignore
import GaugeChart from './components/GaugeChart.vue'
// @ts-ignore
import BarChart from './components/BarChart.vue'
// @ts-ignore
import InfoCard from './components/InfoCard/Index.vue'
// @ts-ignore
import ScoreAssessment from './components/ScoreAssessment/Index.vue'
// @ts-ignore
import RecentRewardsPunishments from './components/RecentRewardsPunishments/Index.vue'
// @ts-ignore
import ConsumptionRecords from './components/ConsumptionRecords/Index.vue'
import { ref, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { DashboardApi } from '@/api/prison/dashboard'
defineOptions({ name: 'Dashboard' })
const route = useRoute()
// 仪表盘数据
const gaugeValue = ref(0)
const gaugeName = ref('')
// 中心左侧数据
const centerLeftData = ref({
top: {
value: '0',
label: '加载中...'
},
middle: {
left: {
value: '0',
label: '加载中...'
},
right: {
value: '0%',
label: '加载中...'
}
},
bottom: {
value: '0位',
label: '加载中...'
}
})
// 中心右侧数据
const centerRightData = ref({
top: {
value: '0',
label: '加载中...'
},
middle: {
left: {
value: '0',
label: '加载中...'
},
right: {
value: '0',
label: '加载中...'
}
},
bottomLeft: {
value: '0位',
label: '加载中...'
},
bottomRight: {
value: '0辆',
label: '加载中...'
}
})
// 柱状图数据
const barChartData = ref<{ category: string; monthlyStandard: number; perCapita: number }[]>([])
// 卡片数据
const barCardData = ref({
inProgress: 0,
toWarehouse: 0,
outWarehouse: 0
})
// 基本信息数据
const basicInfo = ref({
district: '',
prisonNumber: '',
sentenceStart: '',
sentenceEnd: '',
sentenceDays: 0,
age: 0,
hometown: '',
education: '',
maritalStatus: '',
children: '',
birthDate: '',
crimeType: '',
previousConvictions: '',
sentence: ''
})
// 心理访谈记录数据
const interviewRecords = ref<{ date: string; content: string }[]>([])
// 计分考核数据
const scoreRecords = ref<{
date: string
score: string
scoreType: 'positive' | 'negative'
finalScore: number
level: 'excellent' | 'good' | 'poor'
levelText: string
}[]>([])
// 消费记录数据
const consumptionRecords = ref<{
date: string
name: string
nameColor: string
category: string
amount: number
}[]>([])
// 汇款记录数据
const remittanceRecords = ref<{
date: string
name: string
nameColor: string
category: string
amount: number
}[]>([])
// 关系人数据
const relationships = ref<{
name: string
relate: string
color: string
}[]>([])
// 奖惩记录数据
const rewardsPunishments = ref<{
date: string
type: string
typeText: string
content: string
}[]>([])
// 当前时间
const currentTime = ref('')
const prisonerName = ref('加载中...')
// 格式化时间
const formatTime = () => {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
const weekday = weekdays[now.getDay()]
const hours = String(now.getHours()).padStart(2, '0')
const minutes = String(now.getMinutes()).padStart(2, '0')
const seconds = String(now.getSeconds()).padStart(2, '0')
return `${year}${month}${day}${weekday} ${hours}:${minutes}:${seconds}`
}
// 加载数据
const loadData = async (prisonerId: number) => {
try {
const res = await DashboardApi.getPrisonerStats(prisonerId)
if (!res) {
prisonerName.value = '未找到该罪犯'
ElMessage.warning('未找到该罪犯信息')
return
}
// 更新罪犯名称
prisonerName.value = res.prisonerName || '未知'
// 更新仪表盘
gaugeValue.value = res.riskScore || 0
gaugeName.value = ''
// 更新中心左侧数据
if (res.centerLeftData) {
centerLeftData.value = {
top: {
value: res.centerLeftData.topValue || '0',
label: res.centerLeftData.topLabel || ''
},
middle: {
left: {
value: res.centerLeftData.middleLeftValue || '0',
label: res.centerLeftData.middleLeftLabel || ''
},
right: {
value: res.centerLeftData.middleRightValue || '0',
label: res.centerLeftData.middleRightLabel || ''
}
},
bottom: {
value: res.centerLeftData.bottomValue || '0位',
label: res.centerLeftData.bottomLabel || ''
}
}
}
// 更新中心右侧数据
if (res.centerRightData) {
centerRightData.value = {
top: {
value: res.centerRightData.topValue || '0',
label: res.centerRightData.topLabel || ''
},
middle: {
left: {
value: res.centerRightData.middleLeftValue || '0',
label: res.centerRightData.middleLeftLabel || ''
},
right: {
value: res.centerRightData.middleRightValue || '0',
label: res.centerRightData.middleRightLabel || ''
}
},
bottomLeft: {
value: res.centerRightData.bottomLeftValue || '0位',
label: res.centerRightData.bottomLeftLabel || ''
},
bottomRight: {
value: res.centerRightData.bottomRightValue || '0辆',
label: res.centerRightData.bottomRightLabel || ''
}
}
}
// 更新柱状图数据
barChartData.value = res.consumptionMonthlyData || []
// 更新消费汇总
if (res.consumptionSummary) {
barCardData.value = {
inProgress: res.consumptionSummary.inProgress || 0,
toWarehouse: res.consumptionSummary.toWarehouse || 0,
outWarehouse: res.consumptionSummary.outWarehouse || 0
}
}
// 更新基本信息
basicInfo.value = {
district: res.prisonArea || '',
prisonNumber: res.prisonerNo || '',
sentenceStart: res.imprisonmentDate || '',
sentenceEnd: res.releaseDate || '',
sentenceDays: res.servedDays || 0,
age: res.age || 0,
hometown: res.nativePlace || '',
education: res.education || '',
maritalStatus: res.maritalStatus || '',
children: res.children || '',
birthDate: res.birthDate || '',
crimeType: res.crimeType || '',
previousConvictions: res.previousConvictions || '',
sentence: res.sentence || ''
}
// 更新访谈记录
interviewRecords.value = res.interviewRecords || []
// 更新计分考核数据
scoreRecords.value = res.scoreRecords || []
// 更新消费记录
consumptionRecords.value = res.consumptionRecords || []
// 更新汇款记录
remittanceRecords.value = res.remittanceRecords || []
// 更新关系人数据
relationships.value = res.relationships || []
// 更新奖惩记录
rewardsPunishments.value = res.rewardsPunishments || []
} catch (error) {
console.error('加载Dashboard数据失败:', error)
ElMessage.error('加载数据失败,请刷新页面重试')
}
}
let timeInterval: NodeJS.Timeout | null = null
onMounted(() => {
// 初始化时间
currentTime.value = formatTime()
// 每秒更新一次时间
timeInterval = setInterval(() => {
currentTime.value = formatTime()
}, 1000)
// 从 URL 获取罪犯 ID 并加载数据
const prisonerId = route.query.prisonerId as string
if (!prisonerId) {
prisonerName.value = '请选择罪犯'
ElMessage.warning('请从服刑人员列表选择要查看的罪犯')
return
}
const id = Number(prisonerId)
if (isNaN(id) || id <= 0) {
prisonerName.value = '无效的罪犯ID'
ElMessage.error('罪犯ID无效')
return
}
loadData(id)
})
onUnmounted(() => {
// 清理定时器
if (timeInterval) {
clearInterval(timeInterval)
}
})
</script>
<style scoped lang="scss">
.dashboard-container {
width: 100%;
height: 100%;
background-image: url('@/assets/imgs/dashboard/dashboard-back.jpg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
padding: 60px 20px 20px;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.prison-name {
position: absolute;
top: 12px;
left: 50%;
transform: translateX(-50%);
color: white;
text-align: center;
width: 100%;
z-index: 1000;
font-size: 28px;
font-weight: bold;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
}
.current-time {
position: absolute;
top: 18px;
right: 32px;
color: white;
font-size: 16px;
z-index: 1000;
font-weight: 500;
}
.dashboard-content {
margin: 0 auto;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 8px;
}
.dashboard-content-top {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
gap: 15px;
}
.dashboard-content-top-left {
width: 25%;
height: 100%;
}
.dashboard-content-top-center {
width: 50%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
border: 1px solid rgba(56, 102, 141, 0.5);
border-radius: 8px;
padding: 12px;
}
.gauge-container {
width: 100%;
height: 70%;
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 12px;
}
.list-container {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 24px;
height: 30%;
}
.list-card-item {
width: 25%;
height: 90%;
background: #2d3d5f;
border: 1px solid rgba(56, 102, 141, 0.5);
display: flex;
padding-left: 15px;
justify-content: start;
align-items: center;
}
.list-card-item-icon {
width: 24px;
height: 24px;
margin-right: 12px;
}
.icon-location {
background: url('@/assets/imgs/dashboard/icon-location.svg') no-repeat center center;
background-size: 100% 100%;
}
.icon-person {
background: url('@/assets/imgs/dashboard/icon-person.svg') no-repeat center center;
background-size: 100% 100%;
}
.icon-person2 {
background: url('@/assets/imgs/dashboard/icon-person2.svg') no-repeat center center;
background-size: 100% 100%;
}
.icon-car {
background: url('@/assets/imgs/dashboard/icon-card.svg') no-repeat center center;
background-size: 100% 100%;
}
.list-card-item-value {
font-size: 18px;
font-weight: bold;
color: #ffffff;
}
.dashboard-content-top-center-data {
width: 30%;
display: flex;
flex-direction: column;
gap: 8px;
padding: 16px 0 10px;
justify-content: space-between;
}
.dashboard-content-top-center-center {
width: 40%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
padding: 8px 0;
}
// 信息卡片通用样式
.info-card-item {
border-radius: 4px;
padding: 12px 16px;
box-shadow: inset 0 0 15px 0 #2b4183;
border: 1px solid #2b4183;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
.card-row {
display: flex;
justify-content: space-between;
gap: 10px;
width: 100%;
}
.card-number {
font-size: 20px;
font-weight: bold;
color: #ffffff;
line-height: 1.3;
margin-bottom: 8px;
white-space: nowrap;
}
.card-label {
font-size: 14px;
color: rgba(255, 255, 255, 0.85);
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-icon {
width: 18px;
height: 18px;
flex-shrink: 0;
margin-top: 2px;
}
.dashboard-content-top-right {
width: 25%;
height: 100%;
}
.dashboard-content-bottom {
flex: 1;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 15px;
}
.dashboard-content-bottom-left {
width: 35%;
height: 100%;
}
.dashboard-content-bottom-center {
width: 28%;
height: 100%;
}
.dashboard-content-bottom-right {
width: 37%;
height: 100%;
border: 1px solid rgba(56, 102, 141, 0.5);
border-radius: 8px;
padding: 12px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.dashboard-content-bottom-right-title {
font-size: 18px;
font-weight: bold;
color: #ffffff;
margin-bottom: 10px;
}
}
</style>