Compare commits

..

No commits in common. "08917db45bb1f2bd4e22e7e7f1331fb326ec11b7" and "7272342fe6955f1d9e1c62e4a2b6a03069cb05a7" have entirely different histories.

9 changed files with 228 additions and 201 deletions

View File

@ -231,13 +231,6 @@ export interface PrisonerDashboardStatsRespVO {
riskScore: number // 风险评估分
riskLevel: number // 风险等级
// 累计数据
violationCount: number // 累计违规次数
praiseDays: string // 累计表扬天数
praiseCount: number // 累计表扬次数
penaltyCount: number // 累计扣分次数
rewardCount: number // 累计加分次数
// 中心数据
centerLeftData: CenterLeftData
centerRightData: CenterRightData

View File

@ -70,9 +70,10 @@ service.interceptors.request.use(
}
}
// 监狱系统:即使 tenantEnable 为 false也尝试获取并设置租户 ID
// 如果缓存中没有租户 ID使用默认值 1
const tenantId = getTenantId() || 1
config.headers['tenant-id'] = tenantId
const tenantId = getTenantId()
if (tenantId) {
config.headers['tenant-id'] = tenantId
}
const method = config.method?.toUpperCase()
// 防止 GET 请求缓存
if (method === 'GET') {

View File

@ -10,19 +10,20 @@
<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 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-field-item">
<div class="field-label">累计违规次数</div>
<div class="field-value">{{ violationCount }}</div>
<div class="info-card-item center-right-card-item">
<div class="card-number">{{ centerLeftData.middle.left.value }}</div>
<div class="card-label">{{ centerLeftData.middle.left.label }}</div>
</div>
<div class="info-field-item">
<div class="field-label">累计表扬次数</div>
<div class="field-value">{{ praiseCount }}</div>
<div class="info-card-item center-right-card-item">
<div class="card-number">{{ centerLeftData.middle.right.value }}</div>
<div class="card-label">{{ centerLeftData.middle.right.label }}</div>
</div>
</div>
</div>
@ -30,19 +31,20 @@
<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 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-field-item">
<div class="field-label">累计扣分次数</div>
<div class="field-value">{{ penaltyCount }}</div>
<div class="info-card-item center-right-card-item">
<div class="card-number">{{ centerRightData.middle.left.value }}</div>
<div class="card-label">{{ centerRightData.middle.left.label }}</div>
</div>
<div class="info-field-item">
<div class="field-label">累计加分次数</div>
<div class="field-value">{{ rewardCount }}</div>
<div class="info-card-item center-right-card-item">
<div class="card-number">{{ centerRightData.middle.right.value }}</div>
<div class="card-label">{{ centerRightData.middle.right.label }}</div>
</div>
</div>
</div>
@ -83,7 +85,7 @@
</div>
<div class="dashboard-content-bottom-right">
<div class="dashboard-content-bottom-right-title">大帐统计</div>
<BarChart :data="barChartData" :balance="balance" />
<BarChart :height="'200px'" :data="barChartData" :card-data="barCardData" />
</div>
</div>
</div>
@ -169,8 +171,12 @@ const centerRightData = ref({
//
const barChartData = ref<{ category: string; monthlyStandard: number; perCapita: number }[]>([])
//
const balance = ref(0)
//
const barCardData = ref({
inProgress: 0,
toWarehouse: 0,
outWarehouse: 0
})
//
const basicInfo = ref({
@ -240,16 +246,6 @@ const rewardsPunishments = ref<{
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()
@ -283,14 +279,13 @@ const loadData = async (prisonerId: number) => {
gaugeName.value = ''
//
const servedDaysValue = res.servedDays || 0;
servedDays.value = servedDaysValue
remainingDays.value = 0;
const servedDays = res.servedDays || 0;
let remainingDays = 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);
remainingDays = Math.max(0, totalDays - servedDays);
}
// -
@ -310,10 +305,6 @@ const loadData = async (prisonerId: number) => {
console.error('获取计分考核数据失败:', error)
}
//
penaltyCount.value = totalPenaltyCount
rewardCount.value = totalRewardCount
// -
let totalViolationCount = 0
try {
@ -328,10 +319,6 @@ const loadData = async (prisonerId: number) => {
console.error('获取狱情收集数据失败:', error)
}
//
violationCount.value = totalViolationCount
praiseCount.value = res.praiseCount || 0
//
if (res.centerLeftData) {
centerLeftData.value = {
@ -387,8 +374,14 @@ const loadData = async (prisonerId: number) => {
//
barChartData.value = res.consumptionMonthlyData || []
//
balance.value = res.balance || 0
//
if (res.consumptionSummary) {
barCardData.value = {
inProgress: res.consumptionSummary.inProgress || 0,
toWarehouse: res.consumptionSummary.toWarehouse || 0,
outWarehouse: res.consumptionSummary.outWarehouse || 0
}
}
//
basicInfo.value = {
@ -396,7 +389,7 @@ const loadData = async (prisonerId: number) => {
prisonNumber: res.prisonerNo || '',
sentenceStart: res.imprisonmentDate || '',
sentenceEnd: res.releaseDate || '',
sentenceDays: res.sentenceDays || 0,
sentenceDays: res.servedDays || 0,
age: res.age || 0,
hometown: res.nativePlace || '',
education: res.education || '',
@ -557,7 +550,7 @@ onUnmounted(() => {
.list-card-item {
width: 25%;
height: 90%;
background: rgba(45, 65, 131, 0.6);
background: #2d3d5f;
border: 1px solid rgba(56, 102, 141, 0.5);
display: flex;
padding-left: 15px;
@ -600,55 +593,6 @@ onUnmounted(() => {
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);
}
}
// 12
.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;

View File

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

View File

@ -11,7 +11,7 @@
<div class="info-tag">{{ basicInfo.district }}</div>
<div class="info-tag">狱政编号: {{ basicInfo.prisonNumber }}</div>
<div class="info-tag">
刑期{{ basicInfo.sentenceStart }} --- {{ basicInfo.sentenceEnd }} 总天数{{
刑期/止日{{ basicInfo.sentenceStart }}---{{ basicInfo.sentenceEnd }} {{
basicInfo.sentenceDays
}}
</div>

View File

@ -2,7 +2,18 @@
<div class="rewards-punishments-container">
<!-- 标题栏 -->
<div class="rewards-header">
<span class="header-title">风险评估</span>
<span class="header-title">近期奖惩</span>
<div class="filter-tabs">
<div
v-for="tab in filterTabs"
:key="tab.value"
class="filter-tab"
:class="{ active: activeFilter === tab.value }"
@click="activeFilter = tab.value"
>
{{ tab.label }}
</div>
</div>
</div>
<!-- 时间线列表 -->
@ -10,7 +21,7 @@
<div class="timeline-content">
<div class="timeline-line"></div>
<div class="timeline-items">
<div v-for="(item, index) in listData" :key="index" class="timeline-item">
<div v-for="(item, index) in filteredList" :key="index" class="timeline-item">
<div class="timeline-dot" :class="item.type"></div>
<div class="timeline-card">
<div class="card-type" :class="item.type">{{ item.typeText }}</div>
@ -24,7 +35,7 @@
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { ref, computed, watch } from 'vue'
defineOptions({ name: 'RecentRewardsPunishments' })
@ -35,9 +46,29 @@ interface RewardPunishmentItem {
content: string //
}
//
const filterTabs = [
{ label: '全部', value: 'all' },
{ label: '奖励记录', value: 'reward' },
{ label: '惩罚记录', value: 'punishment' }
]
const activeFilter = ref<string>('all')
// - 使 ref
const listData = ref<RewardPunishmentItem[]>([])
//
const filteredList = computed(() => {
if (activeFilter.value === 'all') {
return listData.value
} else if (activeFilter.value === 'reward') {
return listData.value.filter((item) => item.type === 'reward')
} else {
return listData.value.filter((item) => item.type === 'danger')
}
})
// props
const props = withDefaults(
defineProps<{
@ -87,6 +118,24 @@ watch(
color: #ffffff;
}
.filter-tabs {
display: flex;
gap: 8px;
}
.filter-tab {
padding: 6px 12px;
font-size: 12px;
color: rgba(255, 255, 255, 0.85);
background: rgba(56, 102, 141, 0.2);
border-radius: 4px;
&.active {
background: #37599d;
color: #ffffff;
}
}
// 线
.timeline-container {
flex: 1 1 0;

View File

@ -351,10 +351,6 @@ watch(
onMounted(() => {
getLoginFormCache()
getTenantByWebsite()
// ID - ID
if (!authUtil.getTenantId()) {
authUtil.setTenantId(Number(loginData.loginForm.tenantName) || 1)
}
})
</script>

View File

@ -77,7 +77,6 @@
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { EvaluationTemplateApi } from '@/api/prison/evaluation'
import { ElMessage } from 'element-plus'
defineOptions({ name: 'EvaluationTemplateForm' })

View File

@ -7,7 +7,7 @@
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['prison:question:create', 'prison:questionnaire:update']"
v-hasPermi="['prison:question:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新建问题
</el-button>
@ -15,7 +15,7 @@
type="success"
plain
@click="openPartDialog"
v-hasPermi="['prison:question:create', 'prison:questionnaire:update']"
v-hasPermi="['prison:question:create']"
>
<Icon icon="ep:folder" class="mr-5px" /> 分区管理
</el-button>
@ -24,7 +24,7 @@
plain
:disabled="checkedIds.length === 0"
@click="handleDeleteBatch"
v-hasPermi="['prison:question:delete', 'prison:questionnaire:update']"
v-hasPermi="['prison:question:delete']"
>
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
</el-button>
@ -135,7 +135,7 @@
link
size="small"
@click="openForm('update', question.id)"
v-hasPermi="['prison:question:update', 'prison:questionnaire:update']"
v-hasPermi="['prison:question:update']"
>
<Icon icon="ep:edit" /> 修改
</el-button>
@ -144,7 +144,7 @@
link
size="small"
@click="handleDelete(question.id)"
v-hasPermi="['prison:question:delete', 'prison:questionnaire:update']"
v-hasPermi="['prison:question:delete']"
>
<Icon icon="ep:delete" /> 删除
</el-button>
@ -193,11 +193,11 @@
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" plain :icon="Plus" @click="addPartition" v-hasPermi="['prison:question:create', 'prison:questionnaire:update']">添加分区</el-button>
<el-button type="primary" plain :icon="Plus" @click="addPartition" v-hasPermi="['prison:question:create']">添加分区</el-button>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="savePartitions" type="primary" v-hasPermi="['prison:question:update', 'prison:questionnaire:update']">保存设置</el-button>
<el-button @click="savePartitions" type="primary" v-hasPermi="['prison:question:update']">保存设置</el-button>
<el-button @click="partDialogVisible = false">取消</el-button>
</template>
</Dialog>