Compare commits

...

12 Commits

Author SHA1 Message Date
08917db45b feat: 扩展问卷问题管理权限,支持问卷管理员权限
- 后端:PrisonQuestionController 接口增加 prison:questionnaire:update 和 prison:questionnaire:query 权限判断
- 前端:QuestionList.vue 按钮增加 prison:questionnaire:update 权限判断
- 允许拥有问卷模板管理权限的用户也能管理问卷问题
2026-01-28 11:50:34 +08:00
5b41d2b23d fix: 导入缺失的ElMessage组件
修复 EvaluationTemplateForm.vue 中 message 未定义的问题
2026-01-28 10:46:15 +08:00
44fa196bd7 fix(frontend): 修复刑期总天数显示错误
- 前端使用后端返回的 sentenceDays 字段而非 servedDays
- 修复后刑期总天数显示正确(1276天而非错误的743天)
2026-01-27 12:11:53 +08:00
13d86f4707 优化 InfoCard 刑期显示:将'刑期起/止日'改为'刑期'并突出显示总天数 2026-01-27 12:05:52 +08:00
0ba25e3492 Revert "fix(dashboard): 修复剩余刑期天数计算逻辑不一致问题"
This reverts commit ae0493428efbb553fa9573cd632187bf2d013a0b.
2026-01-27 11:57:32 +08:00
ae0493428e fix(dashboard): 修复剩余刑期天数计算逻辑不一致问题
- 前端直接使用后端返回的remainingDays,不再重新计算
- 添加TypeScript接口missing的remainingDays字段定义
- 修复前后端数据不一致问题(差异可达25天)

分析发现:前端用"总刑期-已服刑"计算,后端用"当前日期到释放日期"计算
2026-01-27 11:55:56 +08:00
4e65bb4300 refactor(dashboard): 移除RecentRewardsPunishments组件的过滤筛选功能
- 移除filter-tabs过滤标签UI
- 移除filterTabs、activeFilter、filteredList逻辑
- 直接显示所有数据,不再按类型过滤
- 简化组件功能

参考用户要求:不需要进行过滤筛选
2026-01-27 11:26:19 +08:00
3793d64d3c style(dashboard): 调整Dashboard为半透明背景样式
- 左侧区域、右侧区域、底部模块改为半透明背景
- 使用 rgba(45, 65, 131, 0.6) 实现半透明效果
- 保持原有的边框和文字样式

参考计划: .sisyphus/plans/dashboard-center-update.md 布局调整要求
2026-01-27 11:20:44 +08:00
a65d4e9280 fix(dashboard): 修改中间区域为对称布局(上1下2排列)
- 左侧:累计服刑天数(上1)+ 累计违规次数、累计表扬次数(下2)
- 右侧:剩余刑期天数(上1)+ 累计扣分次数、累计加分次数(下2)
- 调整CSS样式(top-field高度50%)
- 确保左右布局对称美观

参考计划: .sisyphus/plans/dashboard-center-update.md 布局要求
2026-01-27 11:13:39 +08:00
9a3044147b feat(dashboard): 修改中间区域为纵向3字段布局
- 左侧区域:累计服刑天数、累计违规次数、累计表扬次数
- 右侧区域:剩余刑期天数、累计扣分次数、累计加分次数
- 底部模块:显示占位符 '-'
- 添加CSS样式适配纵向布局(info-field-item, field-label, field-value)
- 遵循Element Plus设计规范

参考计划: .sisyphus/plans/dashboard-center-update.md Tasks 2-5
2026-01-27 10:50:21 +08:00
4bff154317 feat(dashboard): 新增累计表扬次数字段类型定义
- 在 PrisonerDashboardStatsRespVO 接口中添加 praiseCount: number
- 新增累计数据分组(violationCount, praiseDays, praiseCount, penaltyCount, rewardCount)

参考计划: .sisyphus/plans/dashboard-center-update.md Task 1
2026-01-27 10:27:54 +08:00
77b78ac64d feat(dashboard): 优化大帐统计展示
- 大帐统计改为显示账户余额,移除出入库卡片
- 柱状图展示收入和支出数据,按月份正序排列
- 奖惩记录从数据库真实查询,区分奖励和惩罚
- 修复惩罚记录显示问题(类型匹配)
- 账户余额显示在图例右侧
- 修复ESLint变量重复声明警告
2026-01-26 18:16:37 +08:00
9 changed files with 201 additions and 228 deletions

View File

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

View File

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

View File

@ -10,20 +10,19 @@
<div class="dashboard-content-top-center"> <div class="dashboard-content-top-center">
<div class="gauge-container"> <div class="gauge-container">
<div class="dashboard-content-top-center-data"> <div class="dashboard-content-top-center-data">
<!-- 左侧第一个卡片 --> <!-- 左侧区域上1下2排列 -->
<div class="info-card-item"> <div class="info-field-item top-field">
<div class="card-number">{{ centerLeftData.top.value }}</div> <div class="field-label">累计服刑天数</div>
<div class="card-label">{{ centerLeftData.top.label }}</div> <div class="field-value">{{ servedDays }}</div>
</div> </div>
<!-- 左侧第二个卡片 -->
<div class="card-row"> <div class="card-row">
<div class="info-card-item center-right-card-item"> <div class="info-field-item">
<div class="card-number">{{ centerLeftData.middle.left.value }}</div> <div class="field-label">累计违规次数</div>
<div class="card-label">{{ centerLeftData.middle.left.label }}</div> <div class="field-value">{{ violationCount }}</div>
</div> </div>
<div class="info-card-item center-right-card-item"> <div class="info-field-item">
<div class="card-number">{{ centerLeftData.middle.right.value }}</div> <div class="field-label">累计表扬次数</div>
<div class="card-label">{{ centerLeftData.middle.right.label }}</div> <div class="field-value">{{ praiseCount }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -31,20 +30,19 @@
<GaugeChart :height="'240px'" :value="gaugeValue" :name="gaugeName" /> <GaugeChart :height="'240px'" :value="gaugeValue" :name="gaugeName" />
</div> </div>
<div class="dashboard-content-top-center-data"> <div class="dashboard-content-top-center-data">
<!-- 右侧第一个卡片 --> <!-- 右侧区域上1下2排列 -->
<div class="info-card-item"> <div class="info-field-item top-field">
<div class="card-number">{{ centerRightData.top.value }}</div> <div class="field-label">剩余刑期天数</div>
<div class="card-label">{{ centerRightData.top.label }}</div> <div class="field-value">{{ remainingDays }}</div>
</div> </div>
<!-- 右侧第二个卡片 -->
<div class="card-row"> <div class="card-row">
<div class="info-card-item center-right-card-item"> <div class="info-field-item">
<div class="card-number">{{ centerRightData.middle.left.value }}</div> <div class="field-label">累计扣分次数</div>
<div class="card-label">{{ centerRightData.middle.left.label }}</div> <div class="field-value">{{ penaltyCount }}</div>
</div> </div>
<div class="info-card-item center-right-card-item"> <div class="info-field-item">
<div class="card-number">{{ centerRightData.middle.right.value }}</div> <div class="field-label">累计加分次数</div>
<div class="card-label">{{ centerRightData.middle.right.label }}</div> <div class="field-value">{{ rewardCount }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -85,7 +83,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 +169,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({
@ -246,6 +240,16 @@ const rewardsPunishments = ref<{
const currentTime = ref('') const currentTime = ref('')
const prisonerName = 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 formatTime = () => {
const now = new Date() const now = new Date()
@ -279,13 +283,14 @@ const loadData = async (prisonerId: number) => {
gaugeName.value = '' gaugeName.value = ''
// //
const servedDays = res.servedDays || 0; const servedDaysValue = res.servedDays || 0;
let remainingDays = 0; servedDays.value = servedDaysValue
remainingDays.value = 0;
if (res.imprisonmentDate && res.releaseDate) { if (res.imprisonmentDate && res.releaseDate) {
const startDate = new Date(res.imprisonmentDate); const startDate = new Date(res.imprisonmentDate);
const endDate = new Date(res.releaseDate); const endDate = new Date(res.releaseDate);
const totalDays = Math.floor((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)); const totalDays = Math.floor((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));
remainingDays = Math.max(0, totalDays - servedDays); remainingDays.value = Math.max(0, totalDays - servedDaysValue);
} }
// - // -
@ -305,6 +310,10 @@ const loadData = async (prisonerId: number) => {
console.error('获取计分考核数据失败:', error) console.error('获取计分考核数据失败:', error)
} }
//
penaltyCount.value = totalPenaltyCount
rewardCount.value = totalRewardCount
// - // -
let totalViolationCount = 0 let totalViolationCount = 0
try { try {
@ -319,6 +328,10 @@ const loadData = async (prisonerId: number) => {
console.error('获取狱情收集数据失败:', error) console.error('获取狱情收集数据失败:', error)
} }
//
violationCount.value = totalViolationCount
praiseCount.value = res.praiseCount || 0
// //
if (res.centerLeftData) { if (res.centerLeftData) {
centerLeftData.value = { centerLeftData.value = {
@ -374,14 +387,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 = {
@ -389,7 +396,7 @@ const loadData = async (prisonerId: number) => {
prisonNumber: res.prisonerNo || '', prisonNumber: res.prisonerNo || '',
sentenceStart: res.imprisonmentDate || '', sentenceStart: res.imprisonmentDate || '',
sentenceEnd: res.releaseDate || '', sentenceEnd: res.releaseDate || '',
sentenceDays: res.servedDays || 0, sentenceDays: res.sentenceDays || 0,
age: res.age || 0, age: res.age || 0,
hometown: res.nativePlace || '', hometown: res.nativePlace || '',
education: res.education || '', education: res.education || '',
@ -550,7 +557,7 @@ onUnmounted(() => {
.list-card-item { .list-card-item {
width: 25%; width: 25%;
height: 90%; height: 90%;
background: #2d3d5f; background: rgba(45, 65, 131, 0.6);
border: 1px solid rgba(56, 102, 141, 0.5); border: 1px solid rgba(56, 102, 141, 0.5);
display: flex; display: flex;
padding-left: 15px; padding-left: 15px;
@ -593,6 +600,55 @@ onUnmounted(() => {
justify-content: space-between; 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 { .dashboard-content-top-center-center {
width: 40%; width: 40%;
height: 220px; height: 220px;

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

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

View File

@ -2,18 +2,7 @@
<div class="rewards-punishments-container"> <div class="rewards-punishments-container">
<!-- 标题栏 --> <!-- 标题栏 -->
<div class="rewards-header"> <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> </div>
<!-- 时间线列表 --> <!-- 时间线列表 -->
@ -21,7 +10,7 @@
<div class="timeline-content"> <div class="timeline-content">
<div class="timeline-line"></div> <div class="timeline-line"></div>
<div class="timeline-items"> <div class="timeline-items">
<div v-for="(item, index) in filteredList" :key="index" class="timeline-item"> <div v-for="(item, index) in listData" :key="index" class="timeline-item">
<div class="timeline-dot" :class="item.type"></div> <div class="timeline-dot" :class="item.type"></div>
<div class="timeline-card"> <div class="timeline-card">
<div class="card-type" :class="item.type">{{ item.typeText }}</div> <div class="card-type" :class="item.type">{{ item.typeText }}</div>
@ -35,7 +24,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, watch } from 'vue'
defineOptions({ name: 'RecentRewardsPunishments' }) defineOptions({ name: 'RecentRewardsPunishments' })
@ -46,29 +35,9 @@ interface RewardPunishmentItem {
content: string // content: string //
} }
//
const filterTabs = [
{ label: '全部', value: 'all' },
{ label: '奖励记录', value: 'reward' },
{ label: '惩罚记录', value: 'punishment' }
]
const activeFilter = ref<string>('all')
// - 使 ref // - 使 ref
const listData = ref<RewardPunishmentItem[]>([]) 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 // props
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -118,24 +87,6 @@ watch(
color: #ffffff; 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 { .timeline-container {
flex: 1 1 0; flex: 1 1 0;

View File

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

View File

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

View File

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