Compare commits

...

4 Commits

Author SHA1 Message Date
59f2fbbfae feat(frontend): 完善评估报告与情况登记功能,优化权限配置 2026-02-03 17:46:40 +08:00
2cee84c00d feat(frontend): 优化风险评估和预警表单交互 2026-02-03 17:46:30 +08:00
11e3ef148b feat(frontend): 增强问卷模块功能,优化囚犯选择器 2026-02-03 17:46:24 +08:00
ec0ff8dc90 feat: 消费记录表单优化
1. ConsumptionDetailDialog: 商品名称/编码为空时显示占位符
2. ConsumptionForm: 服刑人员姓名和罪犯编号设为只读不可修改
2026-02-02 17:45:55 +08:00
15 changed files with 160 additions and 80 deletions

View File

@ -89,10 +89,18 @@ export const PrisonerApi = {
getPage: (params: PageParam) => {
return request.get({ url: '/prison/prisoner/page', params })
},
// 分页查询(别名,用于选择犯人弹窗)
getPrisonerPage: (params: PageParam) => {
return request.get({ url: '/prison/prisoner/page', params })
},
// 获取详情
get: (id: number) => {
return request.get({ url: '/prison/prisoner/get', params: { id } })
},
// 获取详情(别名,与其他模块保持一致)
getPrisoner: (id: number) => {
return request.get({ url: '/prison/prisoner/get', params: { id } })
},
// 创建
create: (data: PrisonerCreateVO) => {
return request.post({ url: '/prison/prisoner/create', data })

View File

@ -20,7 +20,7 @@ export interface RiskAssessment {
id: number // 评估ID
prisonerId?: number // 罪犯ID
prisonerNo?: string // 罪犯编号
prisonerName?: string // 罪犯姓名
prisonerName?: string // 罪犯姓名(用于回显)
assessmentType?: number // 评估类型1-入狱评估 2-定期评估 3-专项评估
assessmentDate?: string // 评估日期
violenceScore: number // 暴力倾向得分
@ -31,7 +31,7 @@ export interface RiskAssessment {
riskFactors: string // 风险因素
suggestions: string // 管控建议
// assessorId 和 assessorName 由后端自动从登录上下文获取,不需要前端传递
nextAssessmentDate: string // 下次评估日期
nextAssessmentDate?: string // 下次评估日期
status?: number // 状态1-待审核 2-已通过
remark: string // 备注
createTime?: string // 创建时间

View File

@ -55,7 +55,15 @@ const whiteList = [
'/register',
'/oauthLogin/gitee',
'/prisoner/prisoner/dashboard', // Dashboard 页面
'/ai-dash-entry' // DashEntry 页面
'/ai-dash-entry', // DashEntry 页面
// 监狱模块路由解决SPA路由重定向问题
'/prison/template', // 问卷模版管理
'/prison/questionnaire', // 问卷任务管理
'/prison/guard', // 狱警管理
// AI指导建议路由evaluation-mgmt
'/prison/evaluation-mgmt/template', // 评估模板管理
'/prison/evaluation-mgmt/dimension', // 评估维度管理
'/prison/evaluation-mgmt/report', // 评估报告管理
]
// 路由加载前

View File

@ -1,8 +1,16 @@
<template>
<Dialog title="消费明细" v-model="dialogVisible" width="600px">
<el-table :data="detailList" v-loading="loading">
<el-table-column label="商品名称" prop="goodsName" align="center" />
<el-table-column label="商品编码" prop="goodsCode" align="center" width="120" />
<el-table-column label="商品名称" prop="goodsName" align="center">
<template #default="{ row }">
{{ row.goodsName || '-' }}
</template>
</el-table-column>
<el-table-column label="商品编码" prop="goodsCode" align="center" width="120">
<template #default="{ row }">
{{ row.goodsCode || '-' }}
</template>
</el-table-column>
<el-table-column label="单价" prop="goodsPrice" align="center" width="100">
<template #default="{ row }">
¥{{ row.goodsPrice?.toFixed(2) }}

View File

@ -9,13 +9,13 @@
>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="罪犯ID" prop="prisonerId">
<el-input v-model="formData.prisonerId" placeholder="请输入罪犯ID" />
<el-form-item label="服刑人员">
<el-input v-model="formData.prisonerName" placeholder="服刑人员姓名" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="罪犯编号" prop="prisonerNo">
<el-input v-model="formData.prisonerNo" placeholder="请输入罪犯编号" />
<el-input v-model="formData.prisonerNo" placeholder="罪犯编号" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
@ -135,6 +135,7 @@ const formType = ref('')
const formData = ref({
id: undefined,
prisonerId: undefined,
prisonerName: undefined,
prisonerNo: undefined,
orderNo: undefined,
type: undefined,
@ -228,6 +229,7 @@ const resetForm = () => {
formData.value = {
id: undefined,
prisonerId: undefined,
prisonerName: undefined,
prisonerNo: undefined,
orderNo: undefined,
type: undefined,

View File

@ -77,12 +77,14 @@
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { EvaluationTemplateApi } from '@/api/prison/evaluation'
import { ElMessage } from 'element-plus'
import { useMessage } from '@/hooks/web/useMessage'
defineOptions({ name: 'EvaluationTemplateForm' })
const emit = defineEmits(['success'])
const message = useMessage()
// ID
const currentTemplateId = ref<number | undefined>(undefined)

View File

@ -163,12 +163,13 @@ const open = async (type: string, templateId: number, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
formData.value.templateId = templateId
resetForm()
formData.value.templateId = templateId
if (id) {
formLoading.value = true
try {
formData.value = await DimensionApi.getDimension(id)
const existingDimension = await DimensionApi.getDimension(id)
formData.value = { ...existingDimension, templateId }
} finally {
formLoading.value = false
}

View File

@ -90,7 +90,7 @@
<!-- 维度配置 Tab -->
<el-tab-pane label="维度配置" name="dimension">
<div class="dimension-header mb-15px">
<el-button type="primary" @click="openDimensionForm(selectedTemplate.id!)" v-hasPermi="['prison:evaluation-report:dimension:create']">
<el-button type="primary" @click="openDimensionForm(selectedTemplate.id!)" v-hasPermi="['prison:evaluation-report:template:update']">
<Icon icon="ep:plus" class="mr-5px" /> 新增维度
</el-button>
</div>
@ -111,7 +111,7 @@
<el-button type="primary" link size="small" @click="openDimensionForm(selectedTemplate.id!, dimension.id)">
编辑
</el-button>
<el-button type="danger" link size="small" @click="handleDeleteDimension(dimension.id!)" v-hasPermi="['prison:evaluation-report:dimension:delete']">
<el-button type="danger" link size="small" @click="handleDeleteDimension(dimension.id!)" v-hasPermi="['prison:evaluation-report:template:update']">
删除
</el-button>
</div>

View File

@ -271,7 +271,7 @@
{{ scope.row.finishTime ? formatDateTime(scope.row.finishTime) : '-' }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="320" fixed="right">
<el-table-column label="操作" align="center" width="400" fixed="right">
<template #default="scope">
<el-button
v-if="scope.row.status === 3"
@ -310,6 +310,15 @@
>
重置
</el-button>
<el-button
link
type="primary"
size="small"
@click="handleViewPrisoner(scope.row)"
v-hasPermi="['prison:prisoner:query']"
>
详情
</el-button>
</template>
</el-table-column>
</el-table>
@ -328,6 +337,9 @@
<!-- 代填弹窗 -->
<AgentFillDialog ref="agentFillDialogRef" @success="loadPrisonerProgress" />
<!-- 服刑人员详情弹窗 -->
<PrisonerDetail ref="prisonerDetailRef" />
</template>
<script setup lang="ts">
@ -336,6 +348,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { formatDateTime } from '@/utils/formatTime'
import AnswerDetailDialog from '@/views/prison/questionnairerecord/AnswerDetailDialog.vue'
import AgentFillDialog from './AgentFillDialog.vue'
import PrisonerDetail from '@/views/prison/prisoner/PrisonerDetail.vue'
import { QuestionnaireTaskApi } from '@/api/prison/questionnaire-task'
defineOptions({ name: 'TaskDetailDialog' })
@ -353,6 +366,9 @@ const answerDetailDialogRef = ref()
//
const agentFillDialogRef = ref()
//
const prisonerDetailRef = ref()
//
const taskDetail = ref<any>(null)
@ -573,6 +589,11 @@ const handleAgentFill = (row: any) => {
agentFillDialogRef.value?.open(row)
}
/** 查看服刑人员详情 */
const handleViewPrisoner = (row: any) => {
prisonerDetailRef.value?.open(row.prisonerId)
}
/** 提醒未完成人员 */
const handleRemind = async () => {
if (!taskId.value) return

View File

@ -117,29 +117,35 @@ const open = async (type: string, id?: number) => {
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as Questionnaire
if (formType.value === 'create') {
await QuestionnaireApi.createQuestionnaire(data)
message.success(t('common.createSuccess'))
} else {
await QuestionnaireApi.updateQuestionnaire(data)
message.success(t('common.updateSuccess'))
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
// coverImage null
formLoading.value = true
try {
const data = { ...formData.value } as unknown as Questionnaire
// coverImage
if (Array.isArray(data.coverImage) && data.coverImage.length > 0) {
data.coverImage = data.coverImage[0] as unknown as string
} else {
data.coverImage = undefined as unknown as string
}
if (formType.value === 'create') {
await QuestionnaireApi.createQuestionnaire(data)
message.success(t('common.createSuccess'))
} else {
await QuestionnaireApi.updateQuestionnaire(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {

View File

@ -192,13 +192,10 @@
</div>
</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-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="partDialogVisible = false">取消</el-button>
<el-button @click="partDialogVisible = false">关闭</el-button>
</template>
</Dialog>
</template>
@ -486,26 +483,39 @@ const savePartitions = async () => {
return
}
//
//
const updates: Array<{ id: number; partName?: string; partSort?: number; sort?: number }> = []
for (let i = 0; i < allPartList.value.length; i++) {
const part = allPartList.value[i]
if (!part.isDefault && part.name) {
for (const p of partitions.value) {
if (p.name === part.name) {
p.questions.forEach((q, sortIndex) => {
updates.push({
id: q.id!,
partName: part.name,
partSort: i,
sort: sortIndex
})
for (const p of partitions.value) {
// name
if ((p.name === part.name) || (p.name === '' && part.name === '')) {
p.questions.forEach((q, sortIndex) => {
updates.push({
id: q.id!,
partName: part.name || undefined,
partSort: i,
sort: sortIndex
})
}
})
}
}
}
//
if (updates.length === 0) {
//
const hasNewPartition = allPartList.value.some(p => !p.isDefault && p.id?.toString().startsWith('part_'))
if (hasNewPartition) {
message.error('请先在该分区下添加问题后再保存')
return
}
//
partDialogVisible.value = false
message.success('保存成功')
return
}
//
await QuestionApi.batchUpdate({ questions: updates })
await getList()
@ -518,20 +528,22 @@ const savePartitions = async () => {
/** 分区拖拽排序完成 */
const onPartitionDragEnd = async () => {
//
//
const updates: Array<{ id: number; partName?: string; partSort?: number; sort?: number }> = []
for (let i = 0; i < partitions.value.length; i++) {
const part = partitions.value[i]
if (part.name) {
part.questions.forEach((q, sortIndex) => {
updates.push({
id: q.id!,
partName: part.name,
partSort: i,
sort: sortIndex
})
part.questions.forEach((q, sortIndex) => {
updates.push({
id: q.id!,
partName: part.name || undefined,
partSort: i,
sort: sortIndex
})
}
})
}
//
if (updates.length === 0) {
return
}
//
await QuestionApi.batchUpdate({ questions: updates })

View File

@ -101,7 +101,11 @@
<dict-tag :type="DICT_TYPE.PRISON_QUESTIONNAIRE_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="问卷说明" align="center" prop="description" width="200" />
<el-table-column label="问卷说明" align="center" prop="description" width="200">
<template #default="scope">
<div v-html="scope.row.description"></div>
</template>
</el-table-column>
<el-table-column label="总分" align="center" prop="totalScore" width="80" />
<el-table-column label="及格分" align="center" prop="passScore" width="80" />
<el-table-column label="状态" align="center" prop="status" width="100">

View File

@ -40,7 +40,7 @@
<el-date-picker
v-model="formData.assessmentDate"
type="date"
value-format="x"
value-format="YYYY-MM-DD"
placeholder="选择评估日期"
/>
</el-form-item>
@ -72,17 +72,12 @@
<el-form-item label="管控建议" prop="suggestions">
<el-input v-model="formData.suggestions" placeholder="请输入管控建议" />
</el-form-item>
<el-form-item label="评估人ID" prop="assessorId">
<el-input v-model="formData.assessorId" placeholder="请输入评估人ID" />
</el-form-item>
<el-form-item label="评估人姓名" prop="assessorName">
<el-input v-model="formData.assessorName" placeholder="请输入评估人姓名" />
</el-form-item>
<!-- 评估人信息由后端自动从登录上下文获取不在前端显示 -->
<el-form-item label="下次评估日期" prop="nextAssessmentDate">
<el-date-picker
v-model="formData.nextAssessmentDate"
type="date"
value-format="x"
value-format="YYYY-MM-DD"
placeholder="选择下次评估日期"
/>
</el-form-item>
@ -129,8 +124,9 @@ const formData = ref({
id: undefined,
prisonerId: undefined as number | undefined,
prisonerNo: undefined as string | undefined,
prisonerName: undefined as string | undefined, //
assessmentType: undefined as number | undefined,
assessmentDate: undefined as number | undefined,
assessmentDate: undefined as string | undefined,
violenceScore: undefined as number | undefined,
escapeScore: undefined as number | undefined,
suicideScore: undefined as number | undefined,
@ -138,9 +134,8 @@ const formData = ref({
riskLevel: undefined as number | undefined,
riskFactors: undefined as string | undefined,
suggestions: undefined as string | undefined,
assessorId: undefined as number | undefined,
assessorName: undefined as string | undefined,
nextAssessmentDate: undefined as number | undefined,
// assessorId assessorName
nextAssessmentDate: undefined as string | undefined,
status: 1 as number | undefined,
remark: undefined as string | undefined
})
@ -153,7 +148,7 @@ const formRules = reactive({
})
const formRef = ref() // Ref
/** 搜索罪犯 */
/** 搜索罪犯 - 支持编号和姓名双条件搜索 */
const searchPrisoner = async (query: string) => {
if (!query) {
prisonerList.value = []
@ -164,6 +159,7 @@ const searchPrisoner = async (query: string) => {
const data = await PrisonerApi.getPage({
pageNo: 1,
pageSize: 20,
prisonerNo: query, //
name: query //
} as any)
prisonerList.value = data.list || []
@ -236,6 +232,7 @@ const resetForm = () => {
id: undefined,
prisonerId: undefined,
prisonerNo: undefined,
prisonerName: undefined,
assessmentType: undefined,
assessmentDate: undefined,
violenceScore: undefined,
@ -245,8 +242,7 @@ const resetForm = () => {
riskLevel: undefined,
riskFactors: undefined,
suggestions: undefined,
assessorId: undefined,
assessorName: undefined,
// assessorId assessorName
nextAssessmentDate: undefined,
status: 1,
remark: undefined

View File

@ -290,9 +290,21 @@ const submitForm = async () => {
//
formLoading.value = true
try {
//
const formatDateTime = (timestamp: number) => {
if (!timestamp) return undefined
const date = new Date(timestamp)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
const data = {
...formData.value,
occurTime: formData.value.occurTime ? new Date(formData.value.occurTime).toISOString() : undefined
occurTime: formatDateTime(formData.value.occurTime)
} as unknown as SituationSaveReqVO
if (formType.value === 'create') {
await SituationApi.createSituation(data)

View File

@ -385,8 +385,8 @@ const submitForm = async () => {
try {
const data = {
...formData.value,
alertTime: formData.value.alertTime ? new Date(formData.value.alertTime).toISOString() : undefined,
occurTime: formData.value.occurTime ? new Date(formData.value.occurTime).toISOString() : undefined
alertTime: formData.value.alertTime ? new Date(formData.value.alertTime).toISOString().slice(0, 19).replace('T', ' ') : undefined,
occurTime: formData.value.occurTime ? new Date(formData.value.occurTime).toISOString().slice(0, 19).replace('T', ' ') : undefined
} as unknown as WarningSaveReqVO
if (formType.value === 'create') {
await WarningApi.createWarning(data)