tangweijie 3793d64d3c style(dashboard): 调整Dashboard为半透明背景样式
- 左侧区域、右侧区域、底部模块改为半透明背景
- 使用 rgba(45, 65, 131, 0.6) 实现半透明效果
- 保持原有的边框和文字样式

参考计划: .sisyphus/plans/dashboard-center-update.md 布局调整要求
2026-01-27 11:20:44 +08:00

751 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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">
<!-- 左侧区域上1下2排列 -->
<div class="info-field-item top-field">
<div class="field-label">累计服刑天数</div>
<div class="field-value">{{ servedDays }}</div>
</div>
<div class="card-row">
<div class="info-field-item">
<div class="field-label">累计违规次数</div>
<div class="field-value">{{ violationCount }}</div>
</div>
<div class="info-field-item">
<div class="field-label">累计表扬次数</div>
<div class="field-value">{{ praiseCount }}</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">
<!-- 右侧区域上1下2排列 -->
<div class="info-field-item top-field">
<div class="field-label">剩余刑期天数</div>
<div class="field-value">{{ remainingDays }}</div>
</div>
<div class="card-row">
<div class="info-field-item">
<div class="field-label">累计扣分次数</div>
<div class="field-value">{{ penaltyCount }}</div>
</div>
<div class="info-field-item">
<div class="field-label">累计加分次数</div>
<div class="field-value">{{ rewardCount }}</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">-</div>
</div>
<div class="list-card-item">
<div class="list-card-item-icon icon-person"></div>
<div class="list-card-item-value">-</div>
</div>
<div class="list-card-item">
<div class="list-card-item-icon icon-person2"></div>
<div class="list-card-item-value">-</div>
</div>
<div class="list-card-item">
<div class="list-card-item-icon icon-car"></div>
<div class="list-card-item-value">-</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 :data="barChartData" :balance="balance" />
</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'
import { SituationApi } from '@/api/prison/situation'
import { ScoreApi } from '@/api/prison/score'
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: '-',
label: '累计表扬天数'
}
})
// 中心右侧数据 - 根据原型设计更新
const centerRightData = ref({
top: {
value: '0',
label: '累计扣分次数'
},
middle: {
left: {
value: '0',
label: '累计加分次数'
},
right: {
value: '-',
label: '本月消费'
}
},
bottomLeft: {
value: '-',
label: '本月奖励'
},
bottomRight: {
value: '-',
label: '本月惩罚'
}
})
// 柱状图数据
const barChartData = ref<{ category: string; monthlyStandard: number; perCapita: number }[]>([])
// 账户余额
const balance = ref(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 servedDays = ref(0)
const violationCount = ref(0)
const praiseCount = ref(0)
// 右侧区域三个字段数据
const remainingDays = ref(0)
const penaltyCount = ref(0)
const rewardCount = ref(0)
// 格式化时间
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 = ''
// 计算累计服刑天数和剩余刑期天数
const servedDaysValue = res.servedDays || 0;
servedDays.value = servedDaysValue
remainingDays.value = 0;
if (res.imprisonmentDate && res.releaseDate) {
const startDate = new Date(res.imprisonmentDate);
const endDate = new Date(res.releaseDate);
const totalDays = Math.floor((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));
remainingDays.value = Math.max(0, totalDays - servedDaysValue);
}
// 获取计分考核数据 - 累计扣分次数和累计加分次数
let totalPenaltyCount = 0;
let totalRewardCount = 0;
try {
const scoreRes = await ScoreApi.getScorePage({
pageNo: 1,
pageSize: 200,
prisonerNo: res.prisonerNo
})
if (scoreRes.list && scoreRes.list.length > 0) {
totalRewardCount = scoreRes.list.filter((item: any) => item.rewardScore > 0).length
totalPenaltyCount = scoreRes.list.filter((item: any) => item.penaltyScore > 0).length
}
} catch (error) {
console.error('获取计分考核数据失败:', error)
}
// 更新右侧区域三个字段数据
penaltyCount.value = totalPenaltyCount
rewardCount.value = totalRewardCount
// 获取狱情收集数据 - 累计违规次数
let totalViolationCount = 0
try {
const situationRes = await SituationApi.getSituationPage({
pageNo: 1,
pageSize: 200
})
if (situationRes.list && situationRes.list.length > 0) {
totalViolationCount = situationRes.list.length
}
} catch (error) {
console.error('获取狱情收集数据失败:', error)
}
// 更新左侧区域三个字段数据
violationCount.value = totalViolationCount
praiseCount.value = res.praiseCount || 0
// 更新中心左侧数据
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 || '暂无',
label: res.centerRightData.bottomRightLabel || '考核等级'
}
}
}
// 更新柱状图数据
barChartData.value = res.consumptionMonthlyData || []
// 更新账户余额
balance.value = res.balance || 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: rgba(45, 65, 131, 0.6);
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: 20px;
padding: 16px 0 10px;
justify-content: space-between;
}
// 纵向字段项样式
.info-field-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 12px 16px;
background: rgba(45, 65, 131, 0.6);
border: 1px solid rgba(56, 102, 141, 0.5);
border-radius: 6px;
box-shadow: inset 0 0 10px 0 rgba(43, 65, 131, 0.3);
transition: all 0.3s ease;
&:hover {
border-color: rgba(56, 102, 141, 0.8);
box-shadow: inset 0 0 15px 0 rgba(43, 65, 131, 0.5);
}
}
// 顶部字段样式用于上1下2布局中的顶部字段
.top-field {
height: 50%;
width: 100%;
}
// 字段标签样式
.field-label {
font-size: 14px;
color: rgba(255, 255, 255, 0.85);
line-height: 1.5;
text-align: center;
white-space: nowrap;
margin-bottom: 8px;
font-weight: 500;
}
// 字段数值样式
.field-value {
font-size: 20px;
font-weight: bold;
color: #ffffff;
line-height: 1.3;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.dashboard-content-top-center-center {
width: 40%;
height: 220px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
padding: 8px 0;
}
// 信息卡片通用样式
.info-card-item {
width: 100%;
height: 50%;
border-radius: 4px;
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;
&.center-right-card-item {
height: 100%;
}
}
.card-row {
display: flex;
justify-content: space-between;
gap: 10px;
width: 100%;
height: 50%;
}
.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: 2vh;
font-weight: bold;
color: #ffffff;
margin-bottom: 1.5vh;
}
}
</style>