diff --git a/src/api/prison/questionnaire-task/index.ts b/src/api/prison/questionnaire-task/index.ts new file mode 100644 index 00000000..66825918 --- /dev/null +++ b/src/api/prison/questionnaire-task/index.ts @@ -0,0 +1,189 @@ +import request from '@/config/axios' + +/** 问卷任务信息 */ +export interface QuestionnaireTask { + id?: number + taskName: string + questionnaireId: number + questionnaireName?: string + targetType: number // 1-指定犯人 2-指定监区 3-全部犯人 + areaId?: number + areaName?: string + prisonerIds?: string + startTime?: string + deadline: string + status: number // 1-草稿 2-进行中 3-已结束 4-已取消 + totalCount?: number + completedCount?: number + pendingCount?: number + completionRate?: string | number + remark?: string + createTime?: string + updateTime?: string +} + +/** 问卷任务分页参数 */ +export interface QuestionnaireTaskPageParams { + pageNo: number + pageSize: number + taskName?: string + questionnaireId?: number + status?: number + targetType?: number + createTime?: string[] +} + +/** 问卷任务创建参数 */ +export interface QuestionnaireTaskCreateParams { + taskName: string + questionnaireId: number + targetType: number + prisonerIds?: number[] + areaId?: number + startTime?: string + deadline: string + remark?: string +} + +/** 问卷任务更新参数 */ +export interface QuestionnaireTaskUpdateParams { + id: number + taskName?: string + deadline?: string + remark?: string +} + +/** 任务进度详情 */ +export interface TaskProgress { + taskId: number + taskName: string + questionnaireName: string + status: number + startTime?: string + deadline: string + totalCount: number + completedCount: number + pendingCount: number + completionRate: string | number + statusBreakdown: { + pending: number + inProgress: number + completed: number + expired: number + cancelled: number + } +} + +/** 按监区统计 */ +export interface TaskAreaStatistics { + areaId?: number + areaName?: string + totalCount: number + completedCount: number + completionRate: string | number + avgScore: string | number + passRate: string | number + riskDistribution: { + highRisk: number + mediumRisk: number + lowRisk: number + } +} + +/** 统计汇总 */ +export interface TaskStatisticsSummary { + taskCount: number + totalPrisoners: number + totalCompleted: number + totalPending: number + overallCompletionRate: string | number +} + +// 问卷任务 API +export const QuestionnaireTaskApi = { + // 查询问卷任务分页 + getQuestionnaireTaskPage: async (params: QuestionnaireTaskPageParams) => { + return await request.get({ url: `/prison/questionnaire-task/page`, params }) + }, + + // 查询问卷任务详情 + getQuestionnaireTask: async (id: number) => { + return await request.get({ url: `/prison/questionnaire-task/get`, params: { id } }) + }, + + // 新增问卷任务 + createQuestionnaireTask: async (data: QuestionnaireTaskCreateParams) => { + return await request.post({ url: `/prison/questionnaire-task/create`, data }) + }, + + // 修改问卷任务 + updateQuestionnaireTask: async (data: QuestionnaireTaskUpdateParams) => { + return await request.put({ url: `/prison/questionnaire-task/update`, data }) + }, + + // 删除问卷任务 + deleteQuestionnaireTask: async (id: number) => { + return await request.delete({ url: `/prison/questionnaire-task/delete`, params: { id } }) + }, + + // 批量删除问卷任务 + deleteQuestionnaireTaskList: async (ids: number[]) => { + return await request.delete({ url: `/prison/questionnaire-task/delete-list`, params: { ids: ids.join(',') } }) + }, + + // 导出问卷任务 Excel + exportQuestionnaireTask: async (params: QuestionnaireTaskPageParams) => { + return await request.download({ url: `/prison/questionnaire-task/export-excel`, params }) + }, + + // ==================== 任务执行相关 ==================== + + // 取消任务 + cancelTask: async (id: number) => { + return await request.post({ url: `/prison/questionnaire-task/cancel`, params: { id } }) + }, + + // 结束任务 + finishTask: async (id: number) => { + return await request.post({ url: `/prison/questionnaire-task/finish`, params: { id } }) + }, + + // 重新开始任务 + restartTask: async (id: number) => { + return await request.post({ url: `/prison/questionnaire-task/restart`, params: { id } }) + }, + + // ==================== 进度跟踪相关 ==================== + + // 获取任务进度 + getTaskProgress: async (id: number) => { + return await request.get({ url: `/prison/questionnaire-task/progress`, params: { id } }) + }, + + // 获取任务未完成人员 + getPendingPrisoners: async (id: number, params: any) => { + return await request.get({ url: `/prison/questionnaire-task/pending-prisoners`, params: { id, ...params } }) + }, + + // 提醒未完成人员 + remindPendingPrisoners: async (id: number) => { + return await request.post({ url: `/prison/questionnaire-task/remind`, params: { id } }) + }, + + // ==================== 统计相关 ==================== + + // 按监区统计任务完成情况 + getTaskAreaStatistics: async (id: number) => { + return await request.get({ url: `/prison/questionnaire-task/area-statistics`, params: { id } }) + }, + + // 获取全局任务统计汇总 + getStatisticsSummary: async () => { + return await request.get({ url: `/prison/questionnaire-task/statistics-summary` }) + }, + + // 按监区对比分析 + compareAreasByQuestionnaire: async (questionnaireId?: number, areaIds?: number[]) => { + return await request.get({ url: `/prison/questionnaire-task/area-comparison`, params: { questionnaireId, areaIds } }) + } +} diff --git a/src/views/Dashboard/Index.vue b/src/views/Dashboard/Index.vue index fd08d042..a342ec4f 100644 --- a/src/views/Dashboard/Index.vue +++ b/src/views/Dashboard/Index.vue @@ -52,19 +52,19 @@
-
108
+
-
-
108
+
-
-
108
+
-
-
108
+
-
@@ -109,6 +109,8 @@ 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' }) @@ -118,51 +120,51 @@ const route = useRoute() const gaugeValue = ref(0) const gaugeName = ref('') -// 中心左侧数据 +// 中心左侧数据 - 根据原型设计更新 const centerLeftData = ref({ top: { value: '0', - label: '加载中...' + label: '累计服刑天数' }, middle: { left: { value: '0', - label: '加载中...' + label: '剩余刑期天数' }, right: { - value: '0%', - label: '加载中...' + value: '0', + label: '累计违规次数' } }, bottom: { - value: '0位', - label: '加载中...' + value: '-', + label: '累计表扬天数' } }) -// 中心右侧数据 +// 中心右侧数据 - 根据原型设计更新 const centerRightData = ref({ top: { value: '0', - label: '加载中...' + label: '累计扣分次数' }, middle: { left: { value: '0', - label: '加载中...' + label: '累计加分次数' }, right: { - value: '0', - label: '加载中...' + value: '-', + label: '本月消费' } }, bottomLeft: { - value: '0位', - label: '加载中...' + value: '-', + label: '本月奖励' }, bottomRight: { - value: '0辆', - label: '加载中...' + value: '-', + label: '本月惩罚' } }) @@ -272,30 +274,71 @@ const loadData = async (prisonerId: number) => { // 更新罪犯名称 prisonerName.value = res.prisonerName || '未知' - // 更新仪表盘 + // 更新仪表盘 - 危险评估分数 gaugeValue.value = res.riskScore || 0 gaugeName.value = '' + // 计算累计服刑天数和剩余刑期天数 + 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 = Math.max(0, totalDays - servedDays); + } + + // 获取计分考核数据 - 累计扣分次数和累计加分次数 + 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) + } + + // 获取狱情收集数据 - 累计违规次数 + 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) + } + // 更新中心左侧数据 if (res.centerLeftData) { centerLeftData.value = { top: { value: res.centerLeftData.topValue || '0', - label: res.centerLeftData.topLabel || '' + label: res.centerLeftData.topLabel || '本月消费' }, middle: { left: { value: res.centerLeftData.middleLeftValue || '0', - label: res.centerLeftData.middleLeftLabel || '' + label: res.centerLeftData.middleLeftLabel || '本月奖励' }, right: { value: res.centerLeftData.middleRightValue || '0', - label: res.centerLeftData.middleRightLabel || '' + label: res.centerLeftData.middleRightLabel || '本月惩罚' } }, bottom: { - value: res.centerLeftData.bottomValue || '0位', - label: res.centerLeftData.bottomLabel || '' + value: res.centerLeftData.bottomValue || '0', + label: res.centerLeftData.bottomLabel || '账户余额' } } } @@ -305,25 +348,25 @@ const loadData = async (prisonerId: number) => { centerRightData.value = { top: { value: res.centerRightData.topValue || '0', - label: res.centerRightData.topLabel || '' + label: res.centerRightData.topLabel || '本月得分' }, middle: { left: { value: res.centerRightData.middleLeftValue || '0', - label: res.centerRightData.middleLeftLabel || '' + label: res.centerRightData.middleLeftLabel || '基础分' }, right: { value: res.centerRightData.middleRightValue || '0', - label: res.centerRightData.middleRightLabel || '' + label: res.centerRightData.middleRightLabel || '加分项' } }, bottomLeft: { - value: res.centerRightData.bottomLeftValue || '0位', - label: res.centerRightData.bottomLeftLabel || '' + value: res.centerRightData.bottomLeftValue || '0', + label: res.centerRightData.bottomLeftLabel || '扣分项' }, bottomRight: { - value: res.centerRightData.bottomRightValue || '0辆', - label: res.centerRightData.bottomRightLabel || '' + value: res.centerRightData.bottomRightValue || '暂无', + label: res.centerRightData.bottomRightLabel || '考核等级' } } } diff --git a/src/views/Dashboard/components/BarChart.vue b/src/views/Dashboard/components/BarChart.vue index 2de2f955..082a5d19 100644 --- a/src/views/Dashboard/components/BarChart.vue +++ b/src/views/Dashboard/components/BarChart.vue @@ -62,8 +62,8 @@ const props = withDefaults( // 创建图表配置 const createChartOption = (): EChartsOption => { const categories = props.data.map((item) => item.category) - const monthlyStandardData = props.data.map((item) => item.monthlyStandard) - const perCapitaData = props.data.map((item) => item.perCapita) + const monthlyStandardData = props.data.map((item) => item.monthlyStandard ?? 0) + const perCapitaData = props.data.map((item) => item.perCapita ?? 0) // 创建底色数据(最大值50) const maxValue = 50 diff --git a/src/views/prison/evaluation-mgmt/report/index.vue b/src/views/prison/evaluation-mgmt/report/index.vue index e5f3aada..16f39810 100644 --- a/src/views/prison/evaluation-mgmt/report/index.vue +++ b/src/views/prison/evaluation-mgmt/report/index.vue @@ -139,6 +139,13 @@ > 查看 + + 预览 + + + + diff --git a/src/views/prison/questionnaire-task/components/PrisonerSelectorDialog.vue b/src/views/prison/questionnaire-task/components/PrisonerSelectorDialog.vue new file mode 100644 index 00000000..f0dd08b4 --- /dev/null +++ b/src/views/prison/questionnaire-task/components/PrisonerSelectorDialog.vue @@ -0,0 +1,200 @@ + + + diff --git a/src/views/prison/questionnaire-task/components/TaskDetailDialog.vue b/src/views/prison/questionnaire-task/components/TaskDetailDialog.vue new file mode 100644 index 00000000..0b444da0 --- /dev/null +++ b/src/views/prison/questionnaire-task/components/TaskDetailDialog.vue @@ -0,0 +1,293 @@ + + + + + diff --git a/src/views/prison/questionnaire-task/index.vue b/src/views/prison/questionnaire-task/index.vue new file mode 100644 index 00000000..9924529d --- /dev/null +++ b/src/views/prison/questionnaire-task/index.vue @@ -0,0 +1,534 @@ + + + + + diff --git a/src/views/prison/questionnaire/components/QuestionList.vue b/src/views/prison/questionnaire/components/QuestionList.vue index 5c44c10a..accb6b65 100644 --- a/src/views/prison/questionnaire/components/QuestionList.vue +++ b/src/views/prison/questionnaire/components/QuestionList.vue @@ -331,30 +331,30 @@ const getList = async () => { list.value = data.list partitions.value = extractPartitions(data.list) - // 初始化分区管理列表(如果还没有初始化) - if (allPartList.value.length === 0) { - // 添加默认分区 - allPartList.value.push({ - id: 'default', - name: '', - sort: 0, - isDefault: true - }) + // 每次都重新初始化分区管理列表(切换问卷时需要重载) + allPartList.value = [] - // 添加已存在的分区 - const existingNames = new Set() - partitions.value.forEach(p => { - if (p.name && !existingNames.has(p.name)) { - existingNames.add(p.name) - allPartList.value.push({ - id: `part_${p.name}`, - name: p.name, - sort: p.sort, - isDefault: false - }) - } - }) - } + // 添加默认分区 + allPartList.value.push({ + id: 'default', + name: '', + sort: 0, + isDefault: true + }) + + // 添加已存在的分区 + const existingNames = new Set() + partitions.value.forEach(p => { + if (p.name && !existingNames.has(p.name)) { + existingNames.add(p.name) + allPartList.value.push({ + id: `part_${p.name}`, + name: p.name, + sort: p.sort, + isDefault: false + }) + } + }) } finally { loading.value = false } diff --git a/src/views/prison/questionnaire/components/QuestionnairePreview.vue b/src/views/prison/questionnaire/components/QuestionnairePreview.vue new file mode 100644 index 00000000..5fec3962 --- /dev/null +++ b/src/views/prison/questionnaire/components/QuestionnairePreview.vue @@ -0,0 +1,524 @@ + + + + + diff --git a/src/views/prison/questionnaire/index.vue b/src/views/prison/questionnaire/index.vue index 474af14f..dbaaf850 100644 --- a/src/views/prison/questionnaire/index.vue +++ b/src/views/prison/questionnaire/index.vue @@ -114,8 +114,16 @@ {{ formatDateTime(scope.row.createTime) }} - + + + diff --git a/src/views/prison/questionnairerecord/index.vue b/src/views/prison/questionnairerecord/index.vue index 9d986e30..c16d6853 100644 --- a/src/views/prison/questionnairerecord/index.vue +++ b/src/views/prison/questionnairerecord/index.vue @@ -159,8 +159,17 @@ {{ formatDateTime(scope.row.createTime) }} - +