feat(prison): Add prisoner management module

- Add prisoner list page with search, pagination, and table display
- Add prisoner form dialog with create/update functionality
- Add API module for prisoner CRUD operations
- Add prison-related enums (supervision level, risk level, status, education)
- Add dict types for prison module dropdown options
- Enable multi-tenant support in frontend configuration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
tangweijie 2026-01-12 22:55:52 +08:00
parent 5d7cb9332c
commit 35af632010
6 changed files with 735 additions and 2 deletions

View File

@ -3,7 +3,7 @@ NODE_ENV=development
VITE_DEV=true
# 请求路径
# 请求路径 - 本地后端服务地址
VITE_BASE_URL='http://localhost:48080'
# 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务
@ -12,6 +12,9 @@ VITE_UPLOAD_TYPE=server
# 接口地址
VITE_API_URL=/admin-api
# 多租户开关
VITE_APP_TENANT_ENABLE=true
# 是否删除debugger
VITE_DROP_DEBUGGER=false

View File

@ -0,0 +1,62 @@
import request from '@/config/axios'
export interface PrisonerVO {
id: number
prisonerNo: string
name: string
gender: number
birthday: string
idCard: string
ethnicity: string
nativePlace: string
education: number
occupation: string
address: string
crime: string
sentenceYears: number
sentenceMonths: number
imprisonmentDate: string
releaseDate: string
supervisionLevel: number
riskLevel: number
prisonAreaId: number
prisonCellId: number
status: number
remark: string
createTime: Date
}
// 服刑人员分页查询
export const getPrisonerPage = (params: PageParam) => {
return request.get({ url: '/prison/prisoner/page', params })
}
// 服刑人员详情
export const getPrisoner = (id: number) => {
return request.get({ url: '/prison/prisoner/get?id=' + id })
}
// 新增服刑人员
export const createPrisoner = (data: PrisonerVO) => {
return request.post({ url: '/prison/prisoner/create', data })
}
// 修改服刑人员
export const updatePrisoner = (data: PrisonerVO) => {
return request.put({ url: '/prison/prisoner/update', data })
}
// 删除服刑人员
export const deletePrisoner = (id: number) => {
return request.delete({ url: '/prison/prisoner/delete?id=' + id })
}
// 批量删除服刑人员
export const deletePrisonerList = (ids: number[]) => {
return request.delete({ url: '/prison/prisoner/delete-list', params: { ids: ids.join(',') } })
}
// 导出服刑人员
export const exportPrisoner = (params) => {
return request.download({ url: '/prison/prisoner/export-excel', params })
}

View File

@ -463,3 +463,47 @@ export const BpmAutoApproveType = {
APPROVE_ALL: 1, // 仅审批一次,后续重复的审批节点均自动通过
APPROVE_SEQUENT: 2 // 仅针对连续审批的节点自动通过
}
// ========== PRISON - 监狱管理模块 ==========
/**
*
*/
export const PrisonSupervisionLevelEnum = {
STRICT: 1, // 严管级
NORMAL: 2, // 普管级
RELAXED: 3 // 宽管级
}
/**
*
*/
export const PrisonRiskLevelEnum = {
LOW: 1, // 低风险
MEDIUM: 2, // 中风险
HIGH: 3, // 高风险
EXTREME: 4 // 极高风险
}
/**
*
*/
export const PrisonerStatusEnum = {
IMPRISONED: 1, // 在押
RELEASED: 2, // 已释放
DECEASED: 3, // 已死亡
PAROLED: 4 // 假释
}
/**
*
*/
export const PrisonEducationEnum = {
PRIMARY: 1, // 小学
MIDDLE: 2, // 初中
HIGH: 3, // 高中
TECHNICAL: 4, // 中专
JUNIOR_COLLEGE: 5, // 大专
UNDERGRADUATE: 6, // 本科
MASTER: 7, // 硕士
DOCTOR: 8 // 博士
}

View File

@ -247,5 +247,11 @@ export enum DICT_TYPE {
IOT_ALERT_RECEIVE_TYPE = 'iot_alert_receive_type', // IoT 告警接收类型
IOT_OTA_TASK_DEVICE_SCOPE = 'iot_ota_task_device_scope', // IoT OTA任务设备范围
IOT_OTA_TASK_STATUS = 'iot_ota_task_status', // IoT OTA 任务状态
IOT_OTA_TASK_RECORD_STATUS = 'iot_ota_task_record_status' // IoT OTA 记录状态
IOT_OTA_TASK_RECORD_STATUS = 'iot_ota_task_record_status', // IoT OTA 记录状态
// ========== PRISON - 监狱管理模块 ==========
PRISON_SUPERVISION_LEVEL = 'prison_supervision_level', // 监管等级
PRISON_RISK_LEVEL = 'prison_risk_level', // 风险等级
PRISONER_STATUS = 'prisoner_status', // 罪犯状态
PRISON_EDUCATION = 'prison_education' // 文化程度
}

View File

@ -0,0 +1,319 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'create' ? '新增服刑人员' : '修改服刑人员'"
width="900px"
:close-on-click-modal="false"
>
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="100px"
v-loading="formLoading"
>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="服刑人员编号" prop="prisonerNo">
<el-input v-model="formData.prisonerNo" placeholder="请输入服刑人员编号" :disabled="dialogType === 'update'" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="姓名" prop="name">
<el-input v-model="formData.name" placeholder="请输入姓名" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="性别" prop="gender">
<el-select v-model="formData.gender" placeholder="请选择性别" clearable style="width: 100%">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="身份证号" prop="idCard">
<el-input v-model="formData.idCard" placeholder="请输入身份证号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="出生日期" prop="birthday">
<el-date-picker
v-model="formData.birthday"
type="date"
placeholder="请选择出生日期"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="民族" prop="ethnicity">
<el-input v-model="formData.ethnicity" placeholder="请输入民族" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="籍贯" prop="nativePlace">
<el-input v-model="formData.nativePlace" placeholder="请输入籍贯" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="文化程度" prop="education">
<el-select v-model="formData.education" placeholder="请选择文化程度" clearable style="width: 100%">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_EDUCATION)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="职业" prop="occupation">
<el-input v-model="formData.occupation" placeholder="请输入职业" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="家庭住址" prop="address">
<el-input v-model="formData.address" placeholder="请输入家庭住址" />
</el-form-item>
</el-col>
</el-row>
<el-divider border-style="dashed">刑罚信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="罪名" prop="crime">
<el-input v-model="formData.crime" placeholder="请输入罪名" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="刑期(年)" prop="sentenceYears">
<el-input-number v-model="formData.sentenceYears" :min="0" :max="100" placeholder="请输入" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="刑期(月)" prop="sentenceMonths">
<el-input-number v-model="formData.sentenceMonths" :min="0" :max="11" placeholder="请输入" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="入狱日期" prop="imprisonmentDate">
<el-date-picker
v-model="formData.imprisonmentDate"
type="date"
placeholder="请选择入狱日期"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="释放日期" prop="releaseDate">
<el-date-picker
v-model="formData.releaseDate"
type="date"
placeholder="请选择释放日期"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="监管等级" prop="supervisionLevel">
<el-select v-model="formData.supervisionLevel" placeholder="请选择监管等级" clearable style="width: 100%">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_SUPERVISION_LEVEL)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="风险等级" prop="riskLevel">
<el-select v-model="formData.riskLevel" placeholder="请选择风险等级" clearable style="width: 100%">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_RISK_LEVEL)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="监区ID" prop="prisonAreaId">
<el-input-number v-model="formData.prisonAreaId" :min="0" placeholder="请输入监区ID" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="监室ID" prop="prisonCellId">
<el-input-number v-model="formData.prisonCellId" :min="0" placeholder="请输入监室ID" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="状态" prop="status">
<el-select v-model="formData.status" placeholder="请选择状态" clearable style="width: 100%">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.PRISONER_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="16">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm" :loading="formLoading">确定</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as PrisonerApi from '@/api/prison/prisoner'
defineOptions({ name: 'PrisonPrisonerForm' })
const { t } = useI18n()
const message = useMessage()
const dialogVisible = ref(false)
const dialogType = ref('create')
const formLoading = ref(false)
const formRef = ref()
const formData = ref({
id: undefined,
prisonerNo: '',
name: '',
gender: undefined,
birthday: '',
idCard: '',
ethnicity: '',
nativePlace: '',
education: undefined,
occupation: '',
address: '',
crime: '',
sentenceYears: 0,
sentenceMonths: 0,
imprisonmentDate: '',
releaseDate: '',
supervisionLevel: undefined,
riskLevel: undefined,
prisonAreaId: undefined,
prisonCellId: undefined,
status: undefined,
remark: ''
})
const rules = {
prisonerNo: [{ required: true, message: '请输入服刑人员编号', trigger: 'blur' }],
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
idCard: [{ required: true, message: '请输入身份证号', trigger: 'blur' }],
crime: [{ required: true, message: '请输入罪名', trigger: 'blur' }],
imprisonmentDate: [{ required: true, message: '请选择入狱日期', trigger: 'change' }],
supervisionLevel: [{ required: true, message: '请选择监管等级', trigger: 'change' }],
riskLevel: [{ required: true, message: '请选择风险等级', trigger: 'change' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
}
/** 打开弹窗 */
const open = (type: string, id?: number) => {
dialogType.value = type
dialogVisible.value = true
resetForm()
if (id) {
getPrisonerDetail(id)
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
prisonerNo: '',
name: '',
gender: undefined,
birthday: '',
idCard: '',
ethnicity: '',
nativePlace: '',
education: undefined,
occupation: '',
address: '',
crime: '',
sentenceYears: 0,
sentenceMonths: 0,
imprisonmentDate: '',
releaseDate: '',
supervisionLevel: undefined,
riskLevel: undefined,
prisonAreaId: undefined,
prisonCellId: undefined,
status: undefined,
remark: ''
}
formRef.value?.resetFields()
}
/** 获取详情 */
const getPrisonerDetail = async (id: number) => {
formLoading.value = true
try {
const data = await PrisonerApi.getPrisoner(id)
formData.value = data
} finally {
formLoading.value = false
}
}
/** 提交表单 */
const submitForm = async () => {
const valid = await formRef.value?.validate()
if (!valid) return
formLoading.value = true
try {
if (dialogType.value === 'create') {
await PrisonerApi.createPrisoner(formData.value)
message.success('新增成功')
} else {
await PrisonerApi.updatePrisoner(formData.value)
message.success('修改成功')
}
dialogVisible.value = false
emit('success')
} finally {
formLoading.value = false
}
}
defineExpose({ open })
</script>

View File

@ -0,0 +1,299 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="100px"
>
<el-form-item label="服刑人员编号" prop="prisonerNo">
<el-input
v-model="queryParams.prisonerNo"
placeholder="请输入服刑人员编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入姓名"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-select
v-model="queryParams.gender"
placeholder="请选择性别"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="监管等级" prop="supervisionLevel">
<el-select
v-model="queryParams.supervisionLevel"
placeholder="请选择监管等级"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_SUPERVISION_LEVEL)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="风险等级" prop="riskLevel">
<el-select
v-model="queryParams.riskLevel"
placeholder="请选择风险等级"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_RISK_LEVEL)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.PRISONER_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['prison:prisoner:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="danger"
plain
:disabled="checkedIds.length === 0"
@click="handleDeleteBatch"
v-hasPermi="['prison:prisoner:delete']"
>
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['prison:prisoner:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" @selection-change="handleRowCheckboxChange">
<el-table-column type="selection" width="55" />
<el-table-column label="服刑人员编号" align="center" prop="prisonerNo" width="120" />
<el-table-column label="姓名" align="center" prop="name" width="100" />
<el-table-column label="性别" align="center" prop="gender" width="80">
<template #default="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.gender" />
</template>
</el-table-column>
<el-table-column label="身份证号" align="center" prop="idCard" width="180" />
<el-table-column label="罪名" align="center" prop="crime" width="150" />
<el-table-column label="监管等级" align="center" prop="supervisionLevel" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PRISON_SUPERVISION_LEVEL" :value="scope.row.supervisionLevel" />
</template>
</el-table-column>
<el-table-column label="风险等级" align="center" prop="riskLevel" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PRISON_RISK_LEVEL" :value="scope.row.riskLevel" />
</template>
</el-table-column>
<el-table-column label="入狱日期" align="center" prop="imprisonmentDate" width="120" />
<el-table-column label="状态" align="center" prop="status" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PRISONER_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200">
<template #default="scope">
<el-button
type="primary"
link
@click="openForm('update', scope.row.id)"
v-hasPermi="['prison:prisoner:update']"
>
修改
</el-button>
<el-button
type="danger"
link
@click="handleDelete(scope.row.id)"
v-hasPermi="['prison:prisoner:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<PrisonerForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { checkPermi } from '@/utils/permission'
import PrisonerForm from './PrisonerForm.vue'
import download from '@/utils/download'
import * as PrisonerApi from '@/api/prison/prisoner'
defineOptions({ name: 'PrisonPrisoner' })
const { t } = useI18n() //
const message = useMessage() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
prisonerNo: undefined,
name: undefined,
gender: undefined,
crime: undefined,
supervisionLevel: undefined,
riskLevel: undefined,
prisonAreaId: undefined,
prisonCellId: undefined,
status: undefined,
imprisonmentDateStart: undefined,
imprisonmentDateEnd: undefined
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await PrisonerApi.getPrisonerPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await PrisonerApi.exportPrisoner(queryParams)
download.excel(data, '服刑人员信息.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await PrisonerApi.deletePrisoner(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 批量删除按钮操作 */
const checkedIds = ref<number[]>([])
const handleRowCheckboxChange = (rows: PrisonerApi.PrisonerVO[]) => {
checkedIds.value = rows.map((row) => row.id)
}
const handleDeleteBatch = async () => {
try {
//
await message.delConfirm()
//
await PrisonerApi.deletePrisonerList(checkedIds.value)
checkedIds.value = []
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>