feat(prison): 新增评估报告等前端页面,优化问卷与罪犯管理
核心变更: 1. 新增页面模块 - 快捷评语管理 (quick-comment) - 报告模板管理 (report-template) - 评估报告编辑 (report) - 风险分析页面 (risk) - 预警管理 (warning) - 服刑情况跟踪 (situation) 2. 功能优化 - 罪犯管理: 新增Workbench工作台页面 - 问卷模块: 完善QuestionForm组件 - 计分考核: 优化ScoreForm支持多种评分方式 - 危险评估: 完善RiskAssessmentForm 3. UI改进 - 登录页面: 新增监狱特色Loading动画 - 罪犯详情: 优化展示效果 - 消费记录: 增强查询功能 4. 基础设施 - 新增JusticeIcon图标组件 - 优化字典格式化工具 - 更新路由配置 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2115e4aa52
commit
bbf4c64391
@ -3,8 +3,8 @@ NODE_ENV=development
|
||||
|
||||
VITE_DEV=true
|
||||
|
||||
# 请求路径 - 本地后端服务地址
|
||||
VITE_BASE_URL='http://localhost:48080'
|
||||
# 请求路径 - 后端服务地址
|
||||
VITE_BASE_URL='http://192.168.9.121:48080'
|
||||
|
||||
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
|
||||
VITE_UPLOAD_TYPE=server
|
||||
|
||||
@ -70,6 +70,10 @@ module.exports = defineConfig({
|
||||
'vue/no-v-html': 'off',
|
||||
'prettier/prettier': 'off', // 芋艿:默认关闭 prettier 的 ESLint 校验,因为我们使用的是 IDE 的 Prettier 插件
|
||||
'@unocss/order': 'off', // 芋艿:禁用 unocss 【css】顺序的提示,因为暂时不需要这么严格,警告也有点繁琐
|
||||
'@unocss/order-attributify': 'off' // 芋艿:禁用 unocss 【属性】顺序的提示,因为暂时不需要这么严格,警告也有点繁琐
|
||||
'@unocss/order-attributify': 'off', // 芋艿:禁用 unocss 【属性】顺序的提示,因为暂时不需要这么严格,警告也有点繁琐
|
||||
|
||||
// 禁止命名空间导入,统一使用默认导入或命名导入
|
||||
'import/default': 'error',
|
||||
'import/no-namespace': 'error'
|
||||
}
|
||||
})
|
||||
|
||||
176
index.html
176
index.html
@ -25,7 +25,7 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
background: #f0f2f5;
|
||||
background: linear-gradient(135deg, #1a365d 0%, #2d3748 50%, #1a365d 100%);
|
||||
}
|
||||
|
||||
.app-loading .app-loading-wrap {
|
||||
@ -41,15 +41,19 @@
|
||||
}
|
||||
|
||||
.app-loading .app-loading-title {
|
||||
margin-bottom: 30px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: #f6e05e;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.app-loading .app-loading-logo {
|
||||
width: 100px;
|
||||
margin: 0 auto 15px auto;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 0 auto 10px auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.app-loading .app-loading-item {
|
||||
@ -58,91 +62,121 @@
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
vertical-align: middle;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.app-loading .app-loading-outter {
|
||||
/* 书本+天平图标 */
|
||||
.app-loading .app-loading-shield {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
animation: bookFloat 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes bookFloat {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 旋转光环 */
|
||||
.app-loading .app-loading-ring {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #ffd700;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
animation: rotateRing 2s linear infinite;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@keyframes rotateRing {
|
||||
from {
|
||||
transform: translate(-50%, -50%) rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: translate(-50%, -50%) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 进度条 */
|
||||
.app-loading .app-loading-progress {
|
||||
width: 200px;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 2px;
|
||||
margin-top: 30px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-loading .app-loading-progress-bar {
|
||||
height: 100%;
|
||||
border: 4px solid #2d8cf0;
|
||||
border-bottom: 0;
|
||||
border-left-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: loader-outter 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite;
|
||||
width: 30%;
|
||||
background: linear-gradient(90deg, #daa520, #ffd700, #daa520);
|
||||
border-radius: 2px;
|
||||
animation: progressMove 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.app-loading .app-loading-inner {
|
||||
position: absolute;
|
||||
top: calc(50% - 20px);
|
||||
left: calc(50% - 20px);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #87bdff;
|
||||
border-right: 0;
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: loader-inner 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loader-outter {
|
||||
@keyframes progressMove {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
width: 10%;
|
||||
margin-left: 0;
|
||||
}
|
||||
50% {
|
||||
width: 50%;
|
||||
margin-left: 25%;
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
width: 10%;
|
||||
margin-left: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loader-outter {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes loader-inner {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(-360deg);
|
||||
transform: rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loader-inner {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(-360deg);
|
||||
transform: rotate(-360deg);
|
||||
}
|
||||
/* 加载文字 */
|
||||
.app-loading .app-loading-text {
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
color: #a0aec0;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
</style>
|
||||
<div class="app-loading">
|
||||
<div class="app-loading-wrap">
|
||||
<div class="app-loading-title">
|
||||
<img src="/logo.gif" class="app-loading-logo" alt="Logo" />
|
||||
<div class="app-loading-title">%VITE_APP_TITLE%</div>
|
||||
</div>
|
||||
<div class="app-loading-title">%VITE_APP_TITLE%</div>
|
||||
<!-- 书本+天平图标 -->
|
||||
<div class="app-loading-item">
|
||||
<div class="app-loading-outter"></div>
|
||||
<div class="app-loading-inner"></div>
|
||||
<svg class="app-loading-shield" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 天平底座 -->
|
||||
<rect x="35" y="75" width="30" height="6" rx="2" fill="#c41e3a" />
|
||||
<rect x="48" y="45" width="4" height="30" fill="#ffd700" />
|
||||
<!-- 天平横杆 -->
|
||||
<rect x="10" y="42" width="80" height="5" rx="2" fill="#ffd700" />
|
||||
<!-- 天平左边盘子(往中间移) -->
|
||||
<path d="M28 47 L38 47 L35 58 L31 58 Z" fill="#ffd700" />
|
||||
<path d="M25 58 Q33 65 41 58" fill="none" stroke="#ffd700" stroke-width="2" />
|
||||
<!-- 天平右边盘子 -->
|
||||
<path d="M62 47 L72 47 L69 58 L65 58 Z" fill="#ffd700" />
|
||||
<path d="M59 58 Q67 65 75 58" fill="none" stroke="#ffd700" stroke-width="2" />
|
||||
<!-- 天平顶装饰 -->
|
||||
<circle cx="50" cy="38" r="5" fill="#c41e3a" />
|
||||
<polygon points="50,30 52,36 58,36 53,40 55,46 50,42 45,46 47,40 42,36 48,36" fill="#ffd700" />
|
||||
<!-- 书本(放在天平左侧) -->
|
||||
<rect x="8" y="48" width="10" height="22" rx="1" fill="#c41e3a" stroke="#ffd700" stroke-width="1.5" />
|
||||
<line x1="13" y1="52" x2="13" y2="68" stroke="#ffd700" stroke-width="0.5" />
|
||||
<line x1="9" y1="56" x2="17" y2="56" stroke="#ffd700" stroke-width="0.5" />
|
||||
<line x1="9" y1="62" x2="17" y2="62" stroke="#ffd700" stroke-width="0.5" />
|
||||
</svg>
|
||||
<div class="app-loading-ring"></div>
|
||||
</div>
|
||||
<div class="app-loading-progress">
|
||||
<div class="app-loading-progress-bar"></div>
|
||||
</div>
|
||||
<div class="app-loading-text">正在加载系统中...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-define-config": "^2.1.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-vue": "^9.22.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
|
||||
@ -5,6 +5,9 @@ export interface ConsumptionPageParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
prisonerNo?: string
|
||||
prisonerName?: string // 罪犯姓名
|
||||
prisonAreaId?: number // 监区ID
|
||||
prisonCellId?: number // 监室ID
|
||||
type?: number
|
||||
status?: number
|
||||
totalAmount?: number
|
||||
@ -26,6 +29,11 @@ export interface Consumption {
|
||||
id: number // 订单ID
|
||||
prisonerId?: number // 罪犯ID
|
||||
prisonerNo?: string // 罪犯编号
|
||||
prisonerName?: string // 罪犯姓名
|
||||
prisonAreaId?: number // 监区ID
|
||||
prisonAreaName?: string // 监区名称
|
||||
prisonCellId?: number // 监室ID
|
||||
prisonCellName?: string // 监室名称
|
||||
orderNo?: string // 订单号
|
||||
type?: number // 类型:1-购物 2-餐饮 3-医疗 4-通讯 5-其他
|
||||
totalAmount?: number // 订单总金额
|
||||
@ -34,6 +42,7 @@ export interface Consumption {
|
||||
status?: number // 状态:1-成功 2-失败
|
||||
remark: string // 备注
|
||||
details?: ConsumptionDetail[] // 消费明细列表
|
||||
createTime?: Date // 创建时间
|
||||
}
|
||||
|
||||
// 消费订单 API
|
||||
|
||||
@ -24,7 +24,7 @@ export interface ChartDataVO {
|
||||
/** 省份数据 */
|
||||
export interface ProvinceChartVO {
|
||||
province: string // 省份名称
|
||||
provinceCode: number // 省份编码
|
||||
provinceCode: string | number // 省份编码(支持字符串名称或数字编码)
|
||||
count: number // 人数
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,19 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
// 性别枚举:0-未知 1-男 2-女
|
||||
export const GENDER_ENUM = {
|
||||
UNKNOWN: 0,
|
||||
MALE: 1,
|
||||
FEMALE: 2
|
||||
} as const
|
||||
|
||||
export type GenderType = typeof GENDER_ENUM[keyof typeof GENDER_ENUM]
|
||||
|
||||
export interface PrisonerVO {
|
||||
id: number
|
||||
prisonerNo: string
|
||||
name: string
|
||||
gender: number
|
||||
gender: number // 性别:0-未知 1-男 2-女
|
||||
genderName?: string // 性别名称
|
||||
birthday: string
|
||||
idCard: string
|
||||
@ -47,7 +56,7 @@ export interface PrisonerCreateVO {
|
||||
id?: number
|
||||
prisonerNo: string
|
||||
name: string
|
||||
gender: number
|
||||
gender: number // 性别:0-未知 1-男 2-女
|
||||
birthday: string
|
||||
idCard: string
|
||||
ethnicity: string
|
||||
@ -74,39 +83,48 @@ export interface PrisonerCreateVO {
|
||||
remark: string
|
||||
}
|
||||
|
||||
// 服刑人员分页查询
|
||||
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: PrisonerCreateVO) => {
|
||||
return request.post({ url: '/prison/prisoner/create', data })
|
||||
}
|
||||
|
||||
// 修改服刑人员
|
||||
export const updatePrisoner = (data: PrisonerCreateVO) => {
|
||||
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 })
|
||||
// PrisonerApi 对象 - 统一使用对象导出模式
|
||||
export const PrisonerApi = {
|
||||
// 分页查询
|
||||
getPage: (params: PageParam) => {
|
||||
return request.get({ url: '/prison/prisoner/page', params })
|
||||
},
|
||||
// 获取详情
|
||||
get: (id: number) => {
|
||||
return request.get({ url: '/prison/prisoner/get', params: { id } })
|
||||
},
|
||||
// 创建
|
||||
create: (data: PrisonerCreateVO) => {
|
||||
return request.post({ url: '/prison/prisoner/create', data })
|
||||
},
|
||||
// 更新
|
||||
update: (data: PrisonerCreateVO) => {
|
||||
return request.put({ url: '/prison/prisoner/update', data })
|
||||
},
|
||||
// 删除
|
||||
delete: (id: number) => {
|
||||
return request.delete({ url: '/prison/prisoner/delete', params: { id } })
|
||||
},
|
||||
// 批量删除
|
||||
deleteList: (ids: number[]) => {
|
||||
return request.delete({ url: '/prison/prisoner/delete-list', params: { ids: ids.join(',') } })
|
||||
},
|
||||
// 导出
|
||||
export: (params: PageParam) => {
|
||||
return request.download({ url: '/prison/prisoner/export-excel', params })
|
||||
},
|
||||
// 调监
|
||||
doTransfer: (data: TransferReqVO) => {
|
||||
return request.post({ url: '/prison/prisoner/transfer', params: data })
|
||||
},
|
||||
// 获取位置历史
|
||||
getAreaHistory: (prisonerId: number) => {
|
||||
return request.get({ url: '/prison/prisoner-area-log/list-by-prisoner-id', params: { prisonerId } })
|
||||
},
|
||||
// 导入
|
||||
import: (data: FormData) => {
|
||||
return request.upload({ url: '/prison/prisoner/import-excel', data })
|
||||
}
|
||||
}
|
||||
|
||||
// 调监请求
|
||||
@ -116,11 +134,6 @@ export interface TransferReqVO {
|
||||
reason?: string
|
||||
}
|
||||
|
||||
// 执行调监
|
||||
export const doTransfer = (data: TransferReqVO) => {
|
||||
return request.post({ url: '/prison/prisoner/transfer', params: data })
|
||||
}
|
||||
|
||||
// 罪犯位置历史记录
|
||||
export interface PrisonerAreaLogVO {
|
||||
id: number
|
||||
@ -142,27 +155,3 @@ export interface PrisonerAreaLogVO {
|
||||
operatorName: string
|
||||
createTime: string
|
||||
}
|
||||
|
||||
// 获取罪犯位置历史
|
||||
export const getPrisonerAreaHistory = (prisonerId: number) => {
|
||||
return request.get({ url: '/prison/prisoner-area-log/list-by-prisoner-id', params: { prisonerId } })
|
||||
}
|
||||
|
||||
// 导入服刑人员
|
||||
export const importPrisoner = (data: FormData) => {
|
||||
return request.upload({ url: '/prison/prisoner/import-excel', data })
|
||||
}
|
||||
|
||||
// PrisonerApi 对象 - 用于组件导入
|
||||
export const PrisonerApi = {
|
||||
getPage: getPrisonerPage,
|
||||
get: getPrisoner,
|
||||
create: createPrisoner,
|
||||
update: updatePrisoner,
|
||||
delete: deletePrisoner,
|
||||
deleteList: deletePrisonerList,
|
||||
export: exportPrisoner,
|
||||
doTransfer: doTransfer,
|
||||
getAreaHistory: getPrisonerAreaHistory,
|
||||
import: importPrisoner
|
||||
}
|
||||
|
||||
@ -3,20 +3,19 @@ import request from '@/config/axios'
|
||||
/** 问卷问题信息 */
|
||||
export interface Question {
|
||||
id?: number // 问题ID(创建时不需要)
|
||||
questionnaireId?: number // 所属问卷ID
|
||||
title?: string // 问题标题
|
||||
type?: number // 问题类型:1-单选 2-多选 3-填空 4-评分 5-日期 6-数字
|
||||
options?: string // 选项JSON
|
||||
questionnaireId: number // 所属问卷ID
|
||||
title: string // 问题标题
|
||||
type: number // 问题类型:1-单选 2-多选 3-填空 4-评分 5-日期 6-数字
|
||||
options?: string // 选项JSON(单选/多选时使用)
|
||||
score?: number // 分值
|
||||
sort?: number // 排序
|
||||
isRequired?: boolean // 是否必答
|
||||
// 新增字段
|
||||
partName?: string // 分区名称
|
||||
partSort?: number // 分区排序
|
||||
helpText?: string // 帮助说明
|
||||
helpText?: string // 帮助说明文字
|
||||
placeholder?: string // 占位提示
|
||||
defaultValue?: string // 默认值
|
||||
autoFillType?: string // 自动填充类型:NONE/AUTO/MANUAL
|
||||
autoFillType?: string // 自动填充类型:NONE-无 AUTO-自动填充 MANUAL-手动输入
|
||||
autoFillSource?: string // 自动填充来源
|
||||
displayCondition?: string // 显示条件JSON
|
||||
minValue?: number // 最小值
|
||||
@ -28,23 +27,20 @@ export interface Question {
|
||||
export interface QuestionPageParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
questionnaireId?: number
|
||||
title?: string
|
||||
type?: number
|
||||
partName?: string
|
||||
}
|
||||
|
||||
/** 批量更新参数 */
|
||||
export interface BatchUpdateQuestion {
|
||||
id: number
|
||||
partName?: string
|
||||
partSort?: number
|
||||
sort?: number
|
||||
questionnaireId?: number // 所属问卷ID
|
||||
title?: string // 问题标题
|
||||
type?: number // 问题类型
|
||||
partName?: string // 分区名称
|
||||
}
|
||||
|
||||
/** 批量更新请求 */
|
||||
export interface BatchUpdateReq {
|
||||
questions: BatchUpdateQuestion[]
|
||||
export interface QuestionBatchUpdateReq {
|
||||
questions: Array<{
|
||||
id: number
|
||||
partName?: string
|
||||
partSort?: number
|
||||
sort?: number
|
||||
}>
|
||||
}
|
||||
|
||||
// 问卷问题 API
|
||||
@ -79,13 +75,13 @@ export const QuestionApi = {
|
||||
return await request.delete<boolean>({ url: `/prison/question/delete-list`, params: { ids: ids.join(',') } })
|
||||
},
|
||||
|
||||
// 批量更新问卷问题
|
||||
batchUpdate: async (data: QuestionBatchUpdateReq) => {
|
||||
return await request.put<boolean>({ url: `/prison/question/batch-update`, data })
|
||||
},
|
||||
|
||||
// 导出问卷问题 Excel
|
||||
exportQuestion: async (params: QuestionPageParams) => {
|
||||
return await request.download({ url: `/prison/question/export-excel`, params })
|
||||
},
|
||||
|
||||
// 批量更新问卷问题(仅排序和分区字段)
|
||||
batchUpdate: async (data: BatchUpdateReq) => {
|
||||
return await request.post<boolean>({ url: `/prison/question/batch-update`, data })
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,18 +27,20 @@ export interface QuestionnairePageParams {
|
||||
title?: string
|
||||
type?: number
|
||||
status?: number
|
||||
description?: string
|
||||
coverImage?: string
|
||||
instruction?: string
|
||||
estimatedTime?: number
|
||||
partCount?: number
|
||||
allowAnonymous?: boolean
|
||||
createTime?: string
|
||||
}
|
||||
|
||||
// 问卷模板 API
|
||||
export const QuestionnaireApi = {
|
||||
// 查询问卷模板分页
|
||||
getQuestionnairePage: async (params: QuestionnairePageParams) => {
|
||||
return await request.get({ url: `/prison/questionnaire/page`, params })
|
||||
return await request.get<{ list: Questionnaire[]; total: number }>({ url: `/prison/questionnaire/page`, params })
|
||||
},
|
||||
|
||||
// 查询问卷模板详情
|
||||
|
||||
@ -19,7 +19,7 @@ export interface QuestionnaireRecord {
|
||||
totalScore?: number // 总分
|
||||
passScore?: number // 及格分数
|
||||
passStatus?: number // 及格状态:1-及格 2-不及格 3-待评阅
|
||||
riskLevel?: number // 风险等级:1-高风险 2-中风险 3-低风险
|
||||
riskLevel?: number // 风险等级:1-低风险 2-中风险 3-高风险 4-极高风险
|
||||
evaluatorId?: number // 评阅人ID
|
||||
evaluatorName?: string // 评阅人姓名
|
||||
evaluateTime?: string // 评阅时间
|
||||
@ -73,7 +73,7 @@ export interface AssessmentManualScoreReq {
|
||||
recordId: number // 测评记录ID
|
||||
subjectiveScore: number // 主观题得分
|
||||
comment?: string // 评语
|
||||
riskLevel?: number // 风险等级:1-高风险 2-中风险 3-低风险
|
||||
riskLevel?: number // 风险等级:1-低风险 2-中风险 3-高风险 4-极高风险
|
||||
}
|
||||
|
||||
/** 分数分布数据 */
|
||||
@ -87,6 +87,7 @@ export interface ScoreDistribution {
|
||||
|
||||
/** 风险分布数据 */
|
||||
export interface RiskDistribution {
|
||||
extreme?: number // 极高风险
|
||||
high?: number
|
||||
medium?: number
|
||||
low?: number
|
||||
|
||||
107
src/api/prison/quick-comment/index.ts
Normal file
107
src/api/prison/quick-comment/index.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
// ============ 快捷评语相关类型 ============
|
||||
|
||||
/** 快捷评语分类 */
|
||||
export interface CommentCategory {
|
||||
id: number
|
||||
name: string
|
||||
type: number // 评估类型:1-入监 2-定期 3-出监 4-减刑 5-专项
|
||||
sort: number
|
||||
status: number
|
||||
}
|
||||
|
||||
/** 快捷评语分页参数 */
|
||||
export interface QuickCommentPageParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
categoryId?: number
|
||||
content?: string
|
||||
status?: number
|
||||
}
|
||||
|
||||
/** 快捷评语 */
|
||||
export interface QuickComment {
|
||||
id: number
|
||||
categoryId: number
|
||||
categoryName?: string // 分类名称
|
||||
content: string // 评语内容
|
||||
usageCount: number // 使用次数
|
||||
sort: number
|
||||
status: number // 0-停用 1-启用
|
||||
creator?: string
|
||||
createTime?: string
|
||||
}
|
||||
|
||||
// ============ 快捷评语分类 API ============
|
||||
|
||||
export const CommentCategoryApi = {
|
||||
// 查询分类列表
|
||||
getList: async (params?: { type?: number; status?: number }) => {
|
||||
return await request.get({ url: '/prison/quick-comment/category/list', params })
|
||||
},
|
||||
|
||||
// 查询分类详情
|
||||
getCategory: async (id: number) => {
|
||||
return await request.get({ url: '/prison/quick-comment/category/get?id=' + id })
|
||||
},
|
||||
|
||||
// 新增分类
|
||||
createCategory: async (data: Partial<CommentCategory>) => {
|
||||
return await request.post({ url: '/prison/quick-comment/category/create', data })
|
||||
},
|
||||
|
||||
// 修改分类
|
||||
updateCategory: async (data: Partial<CommentCategory>) => {
|
||||
return await request.put({ url: '/prison/quick-comment/category/update', data })
|
||||
},
|
||||
|
||||
// 删除分类
|
||||
deleteCategory: async (id: number) => {
|
||||
return await request.delete({ url: '/prison/quick-comment/category/delete?id=' + id })
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 快捷评语 API ============
|
||||
|
||||
export const QuickCommentApi = {
|
||||
// 查询评语分页
|
||||
getPage: async (params: QuickCommentPageParams) => {
|
||||
return await request.get({ url: '/prison/quick-comment/page', params })
|
||||
},
|
||||
|
||||
// 查询评语详情
|
||||
get: async (id: number) => {
|
||||
return await request.get({ url: '/prison/quick-comment/get?id=' + id })
|
||||
},
|
||||
|
||||
// 新增评语
|
||||
create: async (data: QuickComment) => {
|
||||
return await request.post({ url: '/prison/quick-comment/create', data })
|
||||
},
|
||||
|
||||
// 修改评语
|
||||
update: async (data: QuickComment) => {
|
||||
return await request.put({ url: '/prison/quick-comment/update', data })
|
||||
},
|
||||
|
||||
// 删除评语
|
||||
delete: async (id: number) => {
|
||||
return await request.delete({ url: '/prison/quick-comment/delete?id=' + id })
|
||||
},
|
||||
|
||||
// 批量删除评语
|
||||
deleteList: async (ids: number[]) => {
|
||||
return await request.delete({ url: '/prison/quick-comment/delete-list?ids=' + ids.join(',') })
|
||||
},
|
||||
|
||||
// 导入评语
|
||||
importComments: async (data: { categoryId: number; contents: string[] }) => {
|
||||
return await request.post({ url: '/prison/quick-comment/import', data })
|
||||
},
|
||||
|
||||
// 导出评语
|
||||
export: async (params: QuickCommentPageParams) => {
|
||||
return await request.download({ url: '/prison/quick-comment/export', params })
|
||||
}
|
||||
}
|
||||
386
src/api/prison/report/index.ts
Normal file
386
src/api/prison/report/index.ts
Normal file
@ -0,0 +1,386 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
// ============ 评估报告模板相关类型 ============
|
||||
|
||||
/** 评估报告模板分页参数 */
|
||||
export interface ReportTemplatePageParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
name?: string
|
||||
type?: number
|
||||
status?: number
|
||||
}
|
||||
|
||||
/** 评估维度配置 */
|
||||
export interface ReportDimension {
|
||||
id?: number
|
||||
name: string // 维度名称
|
||||
aiPrompt?: string // AI提示词
|
||||
dataSources: string[] // 数据源绑定
|
||||
outputFormat: string // 输出格式:text/paragraph/list
|
||||
enableAi: boolean // 是否AI生成
|
||||
editorType: string // 编辑器类型:text/richtext/select
|
||||
sort: number // 排序
|
||||
}
|
||||
|
||||
/** 评估报告模板 */
|
||||
export interface ReportTemplate {
|
||||
id: number
|
||||
name: string // 模板名称
|
||||
type: number // 模板类型:1-入监综合评估 2-定期考核报告 3-出监评估 4-减刑假释建议 5-专项评估
|
||||
titleFormat: string // 报告标题格式
|
||||
dimensions: ReportDimension[] // 评估维度
|
||||
aiPromptConfig?: string // AI提示词配置(JSON)
|
||||
styleConfig?: string // 样式配置(JSON)
|
||||
status: number // 状态:0-停用 1-启用
|
||||
isDefault: boolean // 是否默认
|
||||
version: number // 版本号
|
||||
remark?: string // 备注
|
||||
creator?: string
|
||||
createTime?: string
|
||||
}
|
||||
|
||||
// ============ 评估报告相关类型 ============
|
||||
|
||||
/** 报告分页参数 */
|
||||
export interface ReportPageParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
reportNo?: string
|
||||
prisonerNo?: string
|
||||
templateId?: number
|
||||
status?: number
|
||||
reportDate?: string[]
|
||||
}
|
||||
|
||||
/** 报告维度内容 */
|
||||
export interface ReportDimensionContent {
|
||||
dimensionId: number
|
||||
dimensionName: string
|
||||
content: string // 内容
|
||||
dataSources?: string // 数据源(JSON)
|
||||
isAiGenerated: boolean // 是否AI生成
|
||||
aiGenerateTime?: string // AI生成时间
|
||||
lastModifyTime?: string // 最后修改时间
|
||||
lastModifyBy?: string // 最后修改人
|
||||
}
|
||||
|
||||
/** 评估报告 */
|
||||
export interface Report {
|
||||
id: number
|
||||
reportNo: string // 报告编号
|
||||
prisonerId: number // 罪犯ID
|
||||
prisonerNo: string // 罪犯编号
|
||||
prisonerName?: string // 罪犯姓名
|
||||
templateId: number // 模板ID
|
||||
templateName?: string // 模板名称
|
||||
title: string // 报告标题
|
||||
reportDate: string // 报告日期
|
||||
dimensions: ReportDimensionContent[] // 维度内容
|
||||
conclusion?: string // 综合结论
|
||||
suggestions?: string // 改造建议
|
||||
riskLevel?: number // 风险等级:1-低风险 2-中风险 3-高风险 4-极高风险
|
||||
attachments?: string[] // 附件列表
|
||||
status: number // 状态:1-草稿 2-待审核 3-已通过 4-已退回
|
||||
version: number // 版本号
|
||||
signature?: string // 数字签名
|
||||
fingerprint?: string // 报告指纹
|
||||
submitterId?: number // 提交人ID
|
||||
submitterName?: string // 提交人姓名
|
||||
submitTime?: string // 提交时间
|
||||
reviewerId?: number // 审核人ID
|
||||
reviewerName?: string // 审核人姓名
|
||||
reviewTime?: string // 审核时间
|
||||
reviewComment?: string // 审核意见
|
||||
remark?: string // 备注
|
||||
creator?: string
|
||||
createTime?: string
|
||||
}
|
||||
|
||||
// ============ 快捷评语相关类型 ============
|
||||
|
||||
/** 快捷评语分类 */
|
||||
export interface CommentCategory {
|
||||
id: number
|
||||
name: string
|
||||
type: number // 评估类型
|
||||
sort: number
|
||||
status: number
|
||||
}
|
||||
|
||||
/** 快捷评语 */
|
||||
export interface QuickComment {
|
||||
id: number
|
||||
categoryId: number
|
||||
categoryName?: string // 分类名称
|
||||
content: string
|
||||
usageCount: number // 使用次数
|
||||
sort: number
|
||||
status: number
|
||||
}
|
||||
|
||||
// ============ 报告版本历史 ============
|
||||
|
||||
/** 报告版本历史 */
|
||||
export interface ReportVersion {
|
||||
id: number
|
||||
reportId: number
|
||||
version: number
|
||||
content: string // 内容快照(JSON)
|
||||
modifierId: number
|
||||
modifierName: string
|
||||
modifyTime: string
|
||||
comment?: string // 版本备注
|
||||
}
|
||||
|
||||
// ============ 评估报告模板 API ============
|
||||
|
||||
export const ReportTemplateApi = {
|
||||
// 查询模板分页
|
||||
getTemplatePage: async (params: ReportTemplatePageParams) => {
|
||||
return await request.get({ url: '/prison/report-template/page', params })
|
||||
},
|
||||
|
||||
// 查询模板详情
|
||||
getTemplate: async (id: number) => {
|
||||
return await request.get({ url: '/prison/report-template/get?id=' + id })
|
||||
},
|
||||
|
||||
// 新增模板
|
||||
createTemplate: async (data: ReportTemplate) => {
|
||||
return await request.post({ url: '/prison/report-template/create', data })
|
||||
},
|
||||
|
||||
// 修改模板
|
||||
updateTemplate: async (data: ReportTemplate) => {
|
||||
return await request.put({ url: '/prison/report-template/update', data })
|
||||
},
|
||||
|
||||
// 删除模板
|
||||
deleteTemplate: async (id: number) => {
|
||||
return await request.delete({ url: '/prison/report-template/delete?id=' + id })
|
||||
},
|
||||
|
||||
// 批量删除模板
|
||||
deleteTemplateList: async (ids: number[]) => {
|
||||
return await request.delete({ url: '/prison/report-template/delete-list?ids=' + ids.join(',') })
|
||||
},
|
||||
|
||||
// 复制模板
|
||||
copyTemplate: async (id: number) => {
|
||||
return await request.post({ url: '/prison/report-template/copy?id=' + id })
|
||||
},
|
||||
|
||||
// 启用/停用模板
|
||||
updateStatus: async (id: number, status: number) => {
|
||||
return await request.put({ url: '/prison/report-template/update-status', params: { id, status } })
|
||||
},
|
||||
|
||||
// 设为默认
|
||||
setDefault: async (id: number) => {
|
||||
return await request.put({ url: '/prison/report-template/set-default?id=' + id })
|
||||
},
|
||||
|
||||
// 导出模板
|
||||
exportTemplate: async (params: ReportTemplatePageParams) => {
|
||||
return await request.download({ url: '/prison/report-template/export-excel', params })
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 评估报告 API ============
|
||||
|
||||
export const ReportApi = {
|
||||
// 查询报告分页
|
||||
getReportPage: async (params: ReportPageParams) => {
|
||||
return await request.get({ url: '/prison/report/page', params })
|
||||
},
|
||||
|
||||
// 查询报告详情
|
||||
getReport: async (id: number) => {
|
||||
return await request.get({ url: '/prison/report/get?id=' + id })
|
||||
},
|
||||
|
||||
// 根据报告编号查询
|
||||
getReportByNo: async (reportNo: string) => {
|
||||
return await request.get({ url: '/prison/report/get-by-no?reportNo=' + reportNo })
|
||||
},
|
||||
|
||||
// 新增报告
|
||||
createReport: async (data: Report) => {
|
||||
return await request.post({ url: '/prison/report/create', data })
|
||||
},
|
||||
|
||||
// 修改报告
|
||||
updateReport: async (data: Report) => {
|
||||
return await request.put({ url: '/prison/report/update', data })
|
||||
},
|
||||
|
||||
// 删除报告
|
||||
deleteReport: async (id: number) => {
|
||||
return await request.delete({ url: '/prison/report/delete?id=' + id })
|
||||
},
|
||||
|
||||
// 批量删除报告
|
||||
deleteReportList: async (ids: number[]) => {
|
||||
return await request.delete({ url: '/prison/report/delete-list?ids=' + ids.join(',') })
|
||||
},
|
||||
|
||||
// 提交审核
|
||||
submitReport: async (id: number) => {
|
||||
return await request.post({ url: '/prison/report/submit?id=' + id })
|
||||
},
|
||||
|
||||
// 审核通过
|
||||
approveReport: async (id: number, comment?: string) => {
|
||||
return await request.post({ url: '/prison/report/approve', data: { id, comment } })
|
||||
},
|
||||
|
||||
// 审核退回
|
||||
rejectReport: async (id: number, comment: string) => {
|
||||
return await request.post({ url: '/prison/report/reject', data: { id, comment } })
|
||||
},
|
||||
|
||||
// AI生成报告
|
||||
generateReportByAi: async (id: number, dimensionIds?: number[]) => {
|
||||
return await request.post({
|
||||
url: '/prison/report/generate-by-ai',
|
||||
data: { id, dimensionIds }
|
||||
})
|
||||
},
|
||||
|
||||
// 批量生成报告
|
||||
batchGenerateReports: async (data: { templateId: number; prisonerIds: number[] }) => {
|
||||
return await request.post({ url: '/prison/report/batch-generate', data })
|
||||
},
|
||||
|
||||
// 获取AI生成进度
|
||||
getGenerateProgress: async (taskId: string) => {
|
||||
return await request.get({ url: '/prison/report/generate-progress?taskId=' + taskId })
|
||||
},
|
||||
|
||||
// 验证报告签名
|
||||
verifySignature: async (id: number) => {
|
||||
return await request.get({ url: '/prison/report/verify-signature?id=' + id })
|
||||
},
|
||||
|
||||
// 导出报告
|
||||
exportReport: async (id: number, format: 'pdf' | 'word') => {
|
||||
return await request.download({ url: '/prison/report/export', params: { id, format } })
|
||||
},
|
||||
|
||||
// 批量导出报告
|
||||
batchExportReports: async (ids: number[], format: 'pdf' | 'word') => {
|
||||
return await request.download({ url: '/prison/report/batch-export', params: { ids: ids.join(','), format } })
|
||||
},
|
||||
|
||||
// 归档报告
|
||||
archiveReport: async (id: number) => {
|
||||
return await request.post({ url: '/prison/report/archive?id=' + id })
|
||||
},
|
||||
|
||||
// 导出报告 Excel
|
||||
exportReportExcel: async (params: ReportPageParams) => {
|
||||
return await request.download({ url: '/prison/report/export-excel', params })
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 快捷评语 API ============
|
||||
|
||||
export const QuickCommentApi = {
|
||||
// 查询评语分类列表
|
||||
getCategoryList: async (params: { type?: number; status?: number }) => {
|
||||
return await request.get({ url: '/prison/quick-comment/category/list', params })
|
||||
},
|
||||
|
||||
// 查询评语分页
|
||||
getCommentPage: async (params: { pageNo: number; pageSize: number; categoryId?: number; keyword?: string }) => {
|
||||
return await request.get({ url: '/prison/quick-comment/page', params })
|
||||
},
|
||||
|
||||
// 新增评语
|
||||
createComment: async (data: QuickComment) => {
|
||||
return await request.post({ url: '/prison/quick-comment/create', data })
|
||||
},
|
||||
|
||||
// 修改评语
|
||||
updateComment: async (data: QuickComment) => {
|
||||
return await request.put({ url: '/prison/quick-comment/update', data })
|
||||
},
|
||||
|
||||
// 删除评语
|
||||
deleteComment: async (id: number) => {
|
||||
return await request.delete({ url: '/prison/quick-comment/delete?id=' + id })
|
||||
},
|
||||
|
||||
// 批量删除评语
|
||||
deleteCommentList: async (ids: number[]) => {
|
||||
return await request.delete({ url: '/prison/quick-comment/delete-list?ids=' + ids.join(',') })
|
||||
},
|
||||
|
||||
// 导入评语
|
||||
importComments: async (data: { categoryId: number; comments: string[] }) => {
|
||||
return await request.post({ url: '/prison/quick-comment/import', data })
|
||||
},
|
||||
|
||||
// 导出评语
|
||||
exportComments: async (categoryId: number) => {
|
||||
return await request.download({ url: '/prison/quick-comment/export', params: { categoryId } })
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 报告版本历史 API ============
|
||||
|
||||
export const ReportVersionApi = {
|
||||
// 查询版本历史
|
||||
getVersionList: async (reportId: number) => {
|
||||
return await request.get({ url: '/prison/report-version/list?reportId=' + reportId })
|
||||
},
|
||||
|
||||
// 获取版本详情
|
||||
getVersion: async (id: number) => {
|
||||
return await request.get({ url: '/prison/report-version/get?id=' + id })
|
||||
},
|
||||
|
||||
// 恢复版本
|
||||
restoreVersion: async (id: number) => {
|
||||
return await request.post({ url: '/prison/report-version/restore?id=' + id })
|
||||
},
|
||||
|
||||
// 对比版本
|
||||
compareVersions: async (versionId1: number, versionId2: number) => {
|
||||
return await request.get({ url: '/prison/report-version/compare', params: { versionId1, versionId2 } })
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 罪犯选择相关 API ============
|
||||
|
||||
/** 罪犯简要信息 */
|
||||
export interface PrisonerBrief {
|
||||
id: number
|
||||
prisonerNo: string
|
||||
name: string
|
||||
areaId: number
|
||||
areaName: string
|
||||
riskLevel?: number // 风险等级
|
||||
}
|
||||
|
||||
/** 罪犯分页参数 */
|
||||
export interface PrisonerPageParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
name?: string
|
||||
prisonerNo?: string
|
||||
areaId?: number
|
||||
}
|
||||
|
||||
export const PrisonerSelectApi = {
|
||||
// 查询罪犯简要信息分页
|
||||
getPrisonerPage: async (params: PrisonerPageParams) => {
|
||||
return await request.get({ url: '/prison/prisoner/brief/page', params })
|
||||
},
|
||||
|
||||
// 查询所有罪犯简要信息(用于选择列表)
|
||||
getAllPrisoners: async (params: { name?: string; prisonerNo?: string }) => {
|
||||
return await request.get({ url: '/prison/prisoner/brief/list', params })
|
||||
}
|
||||
}
|
||||
123
src/api/prison/risk/index.ts
Normal file
123
src/api/prison/risk/index.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
/** 风险评估信息 */
|
||||
export interface Risk {
|
||||
id: number // 评估ID
|
||||
prisonerId?: number // 罪犯ID
|
||||
prisonerCode?: string // 罪犯编号
|
||||
prisonerName?: string // 罪犯姓名
|
||||
assessmentType?: number // 评估类型:1-入监评估 2-定期评估 3-专项评估 4-出监评估
|
||||
assessmentDate?: Date // 评估日期
|
||||
assessMethod?: number // 评估方式:1-量表评估 2-民警评估 3-综合评估
|
||||
overallScore?: number // 综合风险得分
|
||||
riskLevel?: number // 风险等级:1-低风险 2-中风险 3-高风险 4-极高风险
|
||||
mentalState?: number // 精神状态:1-正常 2-异常
|
||||
escapeRisk?: number // 脱逃风险:1-低 2-中 3-高
|
||||
violenceRisk?: number // 暴力倾向:1-低 2-中 3-高
|
||||
revoltRisk?: number // 抗改风险:1-低 2-中 3-高
|
||||
selfHarmRisk?: number // 自杀自伤:1-低 2-中 3-高
|
||||
recommendation?: string // 建议
|
||||
assessor?: string // 评估人
|
||||
conclusion?: string // 结论
|
||||
itemScores?: string // 项目得分JSON
|
||||
remark?: string // 备注
|
||||
createTime?: Date // 创建时间
|
||||
}
|
||||
|
||||
// 风险评估创建/更新请求
|
||||
export interface RiskSaveReqVO {
|
||||
id?: number
|
||||
prisonerId?: number
|
||||
prisonerCode?: string
|
||||
prisonerName?: string
|
||||
assessmentType?: number
|
||||
assessmentDate?: string
|
||||
assessMethod?: number
|
||||
overallScore?: number
|
||||
riskLevel?: number
|
||||
mentalState?: number
|
||||
escapeRisk?: number
|
||||
violenceRisk?: number
|
||||
revoltRisk?: number
|
||||
selfHarmRisk?: number
|
||||
recommendation?: string
|
||||
assessor?: string
|
||||
conclusion?: string
|
||||
itemScores?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
// 风险评估分页查询
|
||||
export interface RiskPageReqVO {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
prisonerId?: number
|
||||
prisonerCode?: string // 罪犯编号
|
||||
prisonerName?: string // 罪犯姓名
|
||||
assessmentType?: number
|
||||
riskLevel?: number
|
||||
assessor?: string
|
||||
assessmentDate?: string
|
||||
}
|
||||
|
||||
// 风险评估详情响应
|
||||
export interface RiskRespVO {
|
||||
id: number
|
||||
prisonerId: number
|
||||
prisonerCode: string // 罪犯编号
|
||||
prisonerName: string // 罪犯姓名
|
||||
assessmentType: number
|
||||
assessmentDate: string
|
||||
assessMethod: number
|
||||
overallScore: number
|
||||
riskLevel: number
|
||||
mentalState: string
|
||||
escapeRisk: string
|
||||
violenceRisk: string
|
||||
revoltRisk: string
|
||||
selfHarmRisk: string
|
||||
recommendation: string
|
||||
assessor: string
|
||||
conclusion: string
|
||||
itemScores: string
|
||||
remark: string
|
||||
createTime: string
|
||||
}
|
||||
|
||||
/** 风险评估 API */
|
||||
export const RiskApi = {
|
||||
// 查询风险评估分页
|
||||
getRiskPage: async (params: RiskPageReqVO) => {
|
||||
return await request.get({ url: `/prison/risk/page`, params })
|
||||
},
|
||||
|
||||
// 查询风险评估详情
|
||||
getRisk: async (id: number) => {
|
||||
return await request.get({ url: `/prison/risk/get?id=` + id })
|
||||
},
|
||||
|
||||
// 新增风险评估
|
||||
createRisk: async (data: RiskSaveReqVO) => {
|
||||
return await request.post({ url: `/prison/risk/create`, data })
|
||||
},
|
||||
|
||||
// 修改风险评估
|
||||
updateRisk: async (data: RiskSaveReqVO) => {
|
||||
return await request.put({ url: `/prison/risk/update`, data })
|
||||
},
|
||||
|
||||
// 删除风险评估
|
||||
deleteRisk: async (id: number) => {
|
||||
return await request.delete({ url: `/prison/risk/delete?id=` + id })
|
||||
},
|
||||
|
||||
/** 批量删除风险评估 */
|
||||
deleteRiskList: async (ids: number[]) => {
|
||||
return await request.delete({ url: `/prison/risk/delete-list?ids=${ids.join(',')}` })
|
||||
},
|
||||
|
||||
// 导出风险评估 Excel
|
||||
exportRisk: async (params) => {
|
||||
return await request.download({ url: `/prison/risk/export-excel`, params })
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,9 @@ import request from '@/config/axios'
|
||||
export interface RiskAssessmentPageParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
prisonerId?: number
|
||||
prisonerNo?: string
|
||||
prisonerName?: string
|
||||
assessmentType?: number
|
||||
riskLevel?: number
|
||||
status?: number
|
||||
@ -15,6 +17,7 @@ export interface RiskAssessment {
|
||||
id: number // 评估ID
|
||||
prisonerId?: number // 罪犯ID
|
||||
prisonerNo?: string // 罪犯编号
|
||||
prisonerName?: string // 罪犯姓名
|
||||
assessmentType?: number // 评估类型:1-入狱评估 2-定期评估 3-专项评估
|
||||
assessmentDate?: string // 评估日期
|
||||
violenceScore: number // 暴力倾向得分
|
||||
|
||||
@ -5,6 +5,9 @@ export interface ScorePageParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
prisonerNo?: string
|
||||
prisonerName?: string // 罪犯姓名
|
||||
prisonAreaId?: number // 监区ID
|
||||
prisonCellId?: number // 监室ID
|
||||
year?: number
|
||||
month?: number
|
||||
level?: number
|
||||
@ -16,6 +19,11 @@ export interface Score {
|
||||
id: number // 记录ID
|
||||
prisonerId?: number // 罪犯ID
|
||||
prisonerNo?: string // 罪犯编号
|
||||
prisonerName?: string // 罪犯姓名
|
||||
prisonAreaId?: number // 监区ID
|
||||
prisonAreaName?: string // 监区名称
|
||||
prisonCellId?: number // 监室ID
|
||||
prisonCellName?: string // 监室名称
|
||||
year?: number // 考核年份
|
||||
month?: number // 考核月份
|
||||
baseScore: number // 基础分
|
||||
@ -27,6 +35,8 @@ export interface Score {
|
||||
assessorName: string // 考核人姓名
|
||||
status?: number // 状态:1-待审核 2-已通过 3-已驳回
|
||||
remark: string // 备注
|
||||
createTime?: Date // 创建时间
|
||||
updateTime?: Date // 更新时间
|
||||
}
|
||||
|
||||
// 计分考核 API
|
||||
|
||||
119
src/api/prison/situation/index.ts
Normal file
119
src/api/prison/situation/index.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import request from '@/config/axios'
|
||||
import { AreaApi } from '@/api/prison/area'
|
||||
|
||||
/** 狱情收集信息 */
|
||||
export interface Situation {
|
||||
id: number // 狱情ID
|
||||
title?: string // 标题
|
||||
content?: string // 详情内容
|
||||
category?: number // 分类:1-监管安全 2-生产安全 3-生活卫生 4-教育改造 5-民警队伍 6-其他
|
||||
level?: number // 等级:1-一般 2-重要 3-紧急
|
||||
source?: number // 来源:1-现场发现 2-耳目反映 3-技术监控 4-他人举报 5-主动报告 6-其他
|
||||
status?: number // 状态:1-待处理 2-处理中 3-已处理 4-已归档
|
||||
areaId?: number // 监区ID
|
||||
cellId?: number // 监室ID
|
||||
reporter?: string // 报告人
|
||||
handler?: string // 处理人
|
||||
occurTime?: Date // 发生时间
|
||||
remark?: string // 备注
|
||||
createTime?: Date // 创建时间
|
||||
}
|
||||
|
||||
// 狱情收集创建/更新请求
|
||||
export interface SituationSaveReqVO {
|
||||
id?: number
|
||||
title: string
|
||||
content?: string
|
||||
category?: number
|
||||
level?: number
|
||||
source?: number
|
||||
status?: number
|
||||
areaId?: number
|
||||
cellId?: number
|
||||
reporter?: string
|
||||
handler?: string
|
||||
occurTime?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
// 狱情收集分页查询
|
||||
export interface SituationPageReqVO {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
title?: string
|
||||
category?: number
|
||||
level?: number
|
||||
source?: number
|
||||
status?: number
|
||||
areaId?: number
|
||||
cellId?: number
|
||||
reporter?: string
|
||||
handler?: string
|
||||
occurTime?: string
|
||||
}
|
||||
|
||||
// 狱情收集详情响应
|
||||
export interface SituationRespVO {
|
||||
id: number
|
||||
title: string
|
||||
content: string
|
||||
category: number
|
||||
level: number
|
||||
source: number
|
||||
status: number
|
||||
areaId: number
|
||||
cellId: number
|
||||
reporter: string
|
||||
handler: string
|
||||
occurTime: string
|
||||
remark: string
|
||||
createTime: string
|
||||
areaName?: string // 监区名称
|
||||
cellName?: string // 监室名称
|
||||
}
|
||||
|
||||
/** 狱情收集 API */
|
||||
export const SituationApi = {
|
||||
// 查询狱情收集分页
|
||||
getSituationPage: async (params: SituationPageReqVO) => {
|
||||
return await request.get({ url: `/prison/situation/page`, params })
|
||||
},
|
||||
|
||||
// 查询狱情收集详情
|
||||
getSituation: async (id: number) => {
|
||||
return await request.get({ url: `/prison/situation/get?id=` + id })
|
||||
},
|
||||
|
||||
// 查询监室列表(用于新增/编辑时选择)
|
||||
getCellList: async (params?: { areaId?: number; status?: number }) => {
|
||||
return await request.get({ url: `/prison/cell/list`, params })
|
||||
},
|
||||
|
||||
// 新增狱情收集
|
||||
createSituation: async (data: SituationSaveReqVO) => {
|
||||
return await request.post({ url: `/prison/situation/create`, data })
|
||||
},
|
||||
|
||||
// 修改狱情收集
|
||||
updateSituation: async (data: SituationSaveReqVO) => {
|
||||
return await request.put({ url: `/prison/situation/update`, data })
|
||||
},
|
||||
|
||||
// 删除狱情收集
|
||||
deleteSituation: async (id: number) => {
|
||||
return await request.delete({ url: `/prison/situation/delete?id=` + id })
|
||||
},
|
||||
|
||||
/** 批量删除狱情收集 */
|
||||
deleteSituationList: async (ids: number[]) => {
|
||||
return await request.delete({ url: `/prison/situation/delete-list?ids=${ids.join(',')}` })
|
||||
},
|
||||
|
||||
// 导出狱情收集 Excel
|
||||
exportSituation: async (params) => {
|
||||
return await request.download({ url: `/prison/situation/export-excel`, params })
|
||||
},
|
||||
|
||||
// 导出 AreaApi 供页面使用
|
||||
AreaApi
|
||||
}
|
||||
174
src/api/prison/warning/index.ts
Normal file
174
src/api/prison/warning/index.ts
Normal file
@ -0,0 +1,174 @@
|
||||
import request from '@/config/axios'
|
||||
import { AreaApi } from '@/api/prison/area'
|
||||
|
||||
/** 预警管理 */
|
||||
export interface Warning {
|
||||
id: number // 预警ID
|
||||
title?: string // 标题
|
||||
content?: string // 内容
|
||||
type?: number // 类型
|
||||
level?: number // 等级
|
||||
status?: number // 状态
|
||||
source?: number // 来源
|
||||
situationId?: number // 关联狱情ID
|
||||
areaId?: number // 监区ID
|
||||
cellId?: number // 监室ID
|
||||
alertTime?: Date // 预警时间
|
||||
verifyTime?: Date // 核实时间
|
||||
verifier?: string // 核实人
|
||||
verifyResult?: string // 核实结果
|
||||
handleTime?: Date // 处置时间
|
||||
handler?: string // 处置人
|
||||
handleResult?: string // 处置结果
|
||||
releaseTime?: Date // 解除时间
|
||||
releaser?: string // 解除人
|
||||
releaseReason?: string // 解除原因
|
||||
occurTime?: Date // 发生时间
|
||||
remark?: string // 备注
|
||||
createTime?: Date // 创建时间
|
||||
}
|
||||
|
||||
// 预警创建/更新请求
|
||||
export interface WarningSaveReqVO {
|
||||
id?: number
|
||||
title: string
|
||||
content?: string
|
||||
type?: number
|
||||
level?: number
|
||||
status?: number
|
||||
source?: number
|
||||
situationId?: number
|
||||
areaId?: number
|
||||
cellId?: number
|
||||
alertTime?: string
|
||||
occurTime?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
// 预警核实请求
|
||||
export interface WarningVerifyReqVO {
|
||||
id: number
|
||||
verifyResult: string
|
||||
}
|
||||
|
||||
// 预警处置请求
|
||||
export interface WarningHandleReqVO {
|
||||
id: number
|
||||
handleResult: string
|
||||
}
|
||||
|
||||
// 预警解除请求
|
||||
export interface WarningReleaseReqVO {
|
||||
id: number
|
||||
releaseReason: string
|
||||
}
|
||||
|
||||
// 预警分页查询
|
||||
export interface WarningPageReqVO {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
title?: string
|
||||
type?: number
|
||||
level?: number
|
||||
status?: number
|
||||
source?: number
|
||||
areaId?: number
|
||||
cellId?: number
|
||||
occurTime?: string
|
||||
}
|
||||
|
||||
// 预警详情响应
|
||||
export interface WarningRespVO {
|
||||
id: number
|
||||
title: string
|
||||
content: string
|
||||
type: number
|
||||
level: number
|
||||
status: number
|
||||
source: number
|
||||
situationId: number
|
||||
areaId: number
|
||||
cellId: number
|
||||
alertTime: string
|
||||
verifyTime: string
|
||||
verifier: string
|
||||
verifyResult: string
|
||||
handleTime: string
|
||||
handler: string
|
||||
handleResult: string
|
||||
releaseTime: string
|
||||
releaser: string
|
||||
releaseReason: string
|
||||
occurTime: string
|
||||
remark: string
|
||||
createTime: string
|
||||
areaName?: string // 监区名称
|
||||
cellName?: string // 监室名称
|
||||
situationTitle?: string // 关联狱情标题
|
||||
}
|
||||
|
||||
/** 预警管理 API */
|
||||
export const WarningApi = {
|
||||
// 查询预警分页
|
||||
getWarningPage: async (params: WarningPageReqVO) => {
|
||||
return await request.get({ url: `/prison/warning/page`, params })
|
||||
},
|
||||
|
||||
// 查询预警详情
|
||||
getWarning: async (id: number) => {
|
||||
return await request.get({ url: `/prison/warning/get?id=` + id })
|
||||
},
|
||||
|
||||
// 查询监室列表
|
||||
getCellList: async (params?: { areaId?: number; status?: number }) => {
|
||||
return await request.get({ url: `/prison/cell/list`, params })
|
||||
},
|
||||
|
||||
// 查询狱情列表
|
||||
getSituationList: async (params?: { status?: number }) => {
|
||||
return await request.get({ url: `/prison/situation/list`, params })
|
||||
},
|
||||
|
||||
// 新增预警
|
||||
createWarning: async (data: WarningSaveReqVO) => {
|
||||
return await request.post({ url: `/prison/warning/create`, data })
|
||||
},
|
||||
|
||||
// 修改预警
|
||||
updateWarning: async (data: WarningSaveReqVO) => {
|
||||
return await request.put({ url: `/prison/warning/update`, data })
|
||||
},
|
||||
|
||||
// 预警核实
|
||||
verifyWarning: async (data: WarningVerifyReqVO) => {
|
||||
return await request.put({ url: `/prison/warning/verify`, data })
|
||||
},
|
||||
|
||||
// 预警处置
|
||||
handleWarning: async (data: WarningHandleReqVO) => {
|
||||
return await request.put({ url: `/prison/warning/handle`, data })
|
||||
},
|
||||
|
||||
// 预警解除
|
||||
releaseWarning: async (data: WarningReleaseReqVO) => {
|
||||
return await request.put({ url: `/prison/warning/release`, data })
|
||||
},
|
||||
|
||||
// 删除预警
|
||||
deleteWarning: async (id: number) => {
|
||||
return await request.delete({ url: `/prison/warning/delete?id=` + id })
|
||||
},
|
||||
|
||||
/** 批量删除预警 */
|
||||
deleteWarningList: async (ids: number[]) => {
|
||||
return await request.delete({ url: `/prison/warning/delete-list?ids=${ids.join(',')}` })
|
||||
},
|
||||
|
||||
// 导出预警 Excel
|
||||
exportWarning: async (params) => {
|
||||
return await request.download({ url: `/prison/warning/export-excel`, params })
|
||||
},
|
||||
|
||||
// 导出 AreaApi 供页面使用
|
||||
AreaApi
|
||||
}
|
||||
41
src/components/Icon/JusticeIcon.vue
Normal file
41
src/components/Icon/JusticeIcon.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<svg :width="size" :height="size" :viewBox="viewBox || '0 0 100 100'" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 天平底座 -->
|
||||
<rect x="35" y="75" width="30" height="6" rx="2" :fill="mainColor" />
|
||||
<rect x="48" y="45" width="4" height="30" :fill="accentColor" />
|
||||
<!-- 天平横杆 -->
|
||||
<rect x="10" y="42" width="80" height="5" rx="2" :fill="accentColor" />
|
||||
<!-- 天平左边盘子(往中间移) -->
|
||||
<path d="M28 47 L38 47 L35 58 L31 58 Z" :fill="accentColor" />
|
||||
<path d="M25 58 Q33 65 41 58" fill="none" :stroke="accentColor" stroke-width="2" />
|
||||
<!-- 天平右边盘子 -->
|
||||
<path d="M62 47 L72 47 L69 58 L65 58 Z" :fill="accentColor" />
|
||||
<path d="M59 58 Q67 65 75 58" fill="none" :stroke="accentColor" stroke-width="2" />
|
||||
<!-- 天平顶装饰 -->
|
||||
<circle cx="50" cy="38" r="5" :fill="mainColor" />
|
||||
<polygon points="50,30 52,36 58,36 53,40 55,46 50,42 45,46 47,40 42,36 48,36" :fill="accentColor" />
|
||||
<!-- 书本(放在天平左侧) -->
|
||||
<rect x="8" y="48" width="10" height="22" rx="1" :fill="mainColor" :stroke="accentColor" stroke-width="1.5" />
|
||||
<line x1="13" y1="52" x2="13" y2="68" :stroke="accentColor" stroke-width="0.5" />
|
||||
<line x1="9" y1="56" x2="17" y2="56" :stroke="accentColor" stroke-width="0.5" />
|
||||
<line x1="9" y1="62" x2="17" y2="62" :stroke="accentColor" stroke-width="0.5" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'JusticeIcon' })
|
||||
|
||||
interface Props {
|
||||
size?: number | string
|
||||
viewBox?: string
|
||||
mainColor?: string
|
||||
accentColor?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
size: 80,
|
||||
viewBox: '0 0 100 100',
|
||||
mainColor: '#c41e3a', // 红色
|
||||
accentColor: '#ffd700' // 金色
|
||||
})
|
||||
</script>
|
||||
@ -1,6 +1,8 @@
|
||||
import type { App } from 'vue'
|
||||
import { Icon } from './Icon'
|
||||
import JusticeIcon from './Icon/JusticeIcon.vue'
|
||||
|
||||
export const setupGlobCom = (app: App<Element>): void => {
|
||||
app.component('Icon', Icon)
|
||||
app.component('JusticeIcon', JusticeIcon)
|
||||
}
|
||||
|
||||
@ -768,6 +768,32 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
hidden: true,
|
||||
canTo: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'situation-platform',
|
||||
component: () => import('@/views/prison/situation/index.vue'),
|
||||
name: 'PrisonSituationPlatform',
|
||||
meta: {
|
||||
title: '狱情收集',
|
||||
icon: 'ep:warning',
|
||||
permission: 'prison:situation:query',
|
||||
noCache: false,
|
||||
hidden: true,
|
||||
canTo: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'report/edit',
|
||||
component: () => import('@/views/prison/report/edit/index.vue'),
|
||||
name: 'PrisonReportEdit',
|
||||
meta: {
|
||||
title: '评估报告编辑',
|
||||
icon: 'ep:document-checked',
|
||||
permission: 'prison:report:update',
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
/**
|
||||
* 数据字典工具类
|
||||
*/
|
||||
import { useDictStoreWithOut } from '@/store/modules/dict'
|
||||
import { useDictStoreWithOut as _useDictStoreWithOut } from '@/store/modules/dict'
|
||||
import { ElementPlusInfoType } from '@/types/elementPlus'
|
||||
|
||||
const dictStore = useDictStoreWithOut()
|
||||
const dictStore = _useDictStoreWithOut()
|
||||
|
||||
// Re-export for convenience
|
||||
export const useDictStoreWithOut = _useDictStoreWithOut
|
||||
|
||||
/**
|
||||
* 获取 dictType 对应的数据字典数组
|
||||
@ -276,5 +279,10 @@ export enum DICT_TYPE {
|
||||
PRISON_CERTIFICATE_TYPE = 'prison_certificate_type', // 证件类型:1-身份证 2-户口簿 3-其他
|
||||
PRISON_ASSESSMENT_STATUS = 'prison_assessment_status', // 测评状态:1-待测评 2-测评中 3-已完成 4-已过期
|
||||
PRISON_ASSESSMENT_PASS_STATUS = 'prison_assessment_pass_status', // 测评及格状态:1-及格 2-不及格 3-待评分
|
||||
PRISON_ASSESSMENT_ANSWER_STATUS = 'prison_assessment_answer_status' // 测评答题状态:1-待评分 2-已评分
|
||||
PRISON_ASSESSMENT_ANSWER_STATUS = 'prison_assessment_answer_status', // 测评答题状态:1-待评分 2-已评分
|
||||
|
||||
// ========== 评估报告模块 ==========
|
||||
PRISON_REPORT_STATUS = 'prison_report_status', // 报告状态:1-草稿 2-待审核 3-已通过 4-已退回
|
||||
PRISON_REPORT_TEMPLATE_TYPE = 'prison_report_template_type', // 报告模板类型:1-入监综合评估 2-定期考核报告 3-出监评估 4-减刑假释建议 5-专项评估
|
||||
PRISON_COMMON_STATUS = 'prison_common_status' // 通用状态:0-停用 1-启用
|
||||
}
|
||||
|
||||
@ -72,6 +72,17 @@ export function formatDate(date: Date, format?: string): string {
|
||||
return date ? dayjs(date).format(format ?? 'YYYY-MM-DD HH:mm:ss') : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期时间为 YYYY-MM-DD HH:mm:ss 格式
|
||||
* @param dateTime 日期时间值
|
||||
*/
|
||||
export function formatDateTime(dateTime: any): string {
|
||||
if (!dateTime) {
|
||||
return ''
|
||||
}
|
||||
return dayjs(dateTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的日期+时间
|
||||
*/
|
||||
@ -198,7 +209,16 @@ export function formatPast2(ms: number): string {
|
||||
* @param cellValue 字段值
|
||||
*/
|
||||
export function dateFormatter(_row: any, _column: TableColumnCtx<any>, cellValue: any): string {
|
||||
return cellValue ? formatDate(cellValue) : ''
|
||||
if (!cellValue) {
|
||||
return ''
|
||||
}
|
||||
// 直接返回已有格式的日期字符串
|
||||
if (typeof cellValue === 'string' && cellValue.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/)) {
|
||||
return cellValue
|
||||
}
|
||||
// 处理其他格式
|
||||
const date = dayjs(cellValue)
|
||||
return date.isValid() ? date.format('YYYY-MM-DD HH:mm:ss') : ''
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,4 +1,14 @@
|
||||
<template>
|
||||
<!-- 自定义监狱风格加载动画 -->
|
||||
<Transition name="fade">
|
||||
<PrisonLoginLoading
|
||||
v-if="customLoadingVisible"
|
||||
:text="loadingText"
|
||||
:background="loadingBackground"
|
||||
:lock="loadingLock"
|
||||
:system-name="loadingSystemName"
|
||||
/>
|
||||
</Transition>
|
||||
<el-form
|
||||
v-show="getShow"
|
||||
ref="formLogin"
|
||||
@ -150,6 +160,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { ElLoading } from 'element-plus'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import PrisonLoginLoading from './PrisonLoginLoading.vue'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
@ -245,7 +256,14 @@ const getTenantByWebsite = async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
const loading = ref() // ElLoading.service 返回的实例
|
||||
const loading = ref() // 自定义Loading实例
|
||||
const customLoadingVisible = ref(false) // 控制自定义加载动画显示
|
||||
|
||||
// 加载动画配置项(保留原有 ElLoading 可配置项)
|
||||
const loadingText = ref('正在加载系统中...') // 提示文字
|
||||
const loadingBackground = ref('rgba(0, 0, 0, 0.7)') // 背景颜色
|
||||
const loadingLock = ref(true) // 是否锁定页面
|
||||
const loadingSystemName = ref('') // 系统名称(可选,默认从 store 获取)
|
||||
// 登录
|
||||
const handleLogin = async (params: any) => {
|
||||
loginLoading.value = true
|
||||
@ -261,11 +279,8 @@ const handleLogin = async (params: any) => {
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
loading.value = ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在加载系统中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
// 使用自定义监狱风格加载动画
|
||||
customLoadingVisible.value = true
|
||||
if (loginDataLoginForm.rememberMe) {
|
||||
authUtil.setLoginForm(loginDataLoginForm)
|
||||
} else {
|
||||
@ -283,7 +298,7 @@ const handleLogin = async (params: any) => {
|
||||
}
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
loading.value.close()
|
||||
customLoadingVisible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -357,4 +372,15 @@ onMounted(() => {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义加载动画过渡效果
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
266
src/views/Login/components/PrisonLoginLoading.vue
Normal file
266
src/views/Login/components/PrisonLoginLoading.vue
Normal file
@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<div class="prison-loading-overlay" :style="{ background: background }">
|
||||
<div class="prison-loading-container">
|
||||
<!-- 盾牌图标 + 旋转光环 -->
|
||||
<div class="shield-wrapper">
|
||||
<div class="shield-glow"></div>
|
||||
<svg class="shield-icon" viewBox="0 0 100 120" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 盾牌主体 -->
|
||||
<defs>
|
||||
<linearGradient id="shieldGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#1e3a5f;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#2c5282;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1e3a5f;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="goldGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#f6e05e;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#d69e2e;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#b7791f;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- 盾牌轮廓 -->
|
||||
<path
|
||||
d="M50 5 L90 25 L90 65 Q90 95 50 115 Q10 95 10 65 L10 25 Z"
|
||||
fill="url(#shieldGradient)"
|
||||
stroke="url(#goldGradient)"
|
||||
stroke-width="3"
|
||||
/>
|
||||
<!-- 盾牌内部装饰 -->
|
||||
<path
|
||||
d="M50 20 L75 35 L75 60 Q75 80 50 95 Q25 80 25 60 L25 35 Z"
|
||||
fill="none"
|
||||
stroke="#d69e2e"
|
||||
stroke-width="1.5"
|
||||
opacity="0.6"
|
||||
/>
|
||||
<!-- 警徽中心 -->
|
||||
<circle cx="50" cy="55" r="18" fill="none" stroke="url(#goldGradient)" stroke-width="2" />
|
||||
<!-- 五角星 -->
|
||||
<polygon
|
||||
points="50,40 53,50 63,50 55,57 58,67 50,61 42,67 45,57 37,50 47,50"
|
||||
fill="url(#goldGradient)"
|
||||
/>
|
||||
</svg>
|
||||
<!-- 旋转的外圈 -->
|
||||
<div class="rotating-ring"></div>
|
||||
</div>
|
||||
|
||||
<!-- 系统名称和加载信息 -->
|
||||
<div class="loading-text">
|
||||
<div class="system-name">{{ systemName }}</div>
|
||||
<div class="loading-message">{{ message }}</div>
|
||||
<!-- 进度条 -->
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill"></div>
|
||||
</div>
|
||||
<div class="loading-dots">
|
||||
<span class="dot"></span>
|
||||
<span class="dot"></span>
|
||||
<span class="dot"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
|
||||
defineOptions({ name: 'PrisonLoginLoading' })
|
||||
|
||||
interface Props {
|
||||
text?: string // 加载提示文字
|
||||
background?: string // 背景颜色
|
||||
lock?: boolean // 是否锁定页面
|
||||
systemName?: string // 系统名称
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
text: '正在加载系统中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)',
|
||||
lock: true,
|
||||
systemName: ''
|
||||
})
|
||||
|
||||
// 获取系统标题,如果未传入则从store获取
|
||||
const appStore = useAppStore()
|
||||
const systemName = computed(() => props.systemName || appStore.getTitle || 'XL监狱综合管理平台')
|
||||
|
||||
// 加载提示文字
|
||||
const message = computed(() => props.text)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.prison-loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.prison-loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.shield-wrapper {
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 144px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.shield-glow {
|
||||
position: absolute;
|
||||
width: 140px;
|
||||
height: 164px;
|
||||
background: radial-gradient(ellipse at center, rgba(214, 158, 46, 0.3) 0%, transparent 70%);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.shield-icon {
|
||||
width: 100px;
|
||||
height: 120px;
|
||||
animation: shieldFloat 3s ease-in-out infinite;
|
||||
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.3));
|
||||
}
|
||||
|
||||
.rotating-ring {
|
||||
position: absolute;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #d69e2e;
|
||||
border-radius: 50%;
|
||||
animation: rotate 3s linear infinite;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 0.3;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shieldFloat {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.system-name {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #f6e05e;
|
||||
letter-spacing: 4px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
margin-bottom: 15px;
|
||||
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
|
||||
}
|
||||
|
||||
.loading-message {
|
||||
font-size: 16px;
|
||||
color: #a0aec0;
|
||||
margin-bottom: 20px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 300px;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
width: 30%;
|
||||
background: linear-gradient(90deg, #d69e2e, #f6e05e, #d69e2e);
|
||||
border-radius: 2px;
|
||||
animation: progressMove 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes progressMove {
|
||||
0% {
|
||||
width: 10%;
|
||||
margin-left: 0;
|
||||
}
|
||||
50% {
|
||||
width: 50%;
|
||||
margin-left: 25%;
|
||||
}
|
||||
100% {
|
||||
width: 10%;
|
||||
margin-left: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-dots {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #d69e2e;
|
||||
border-radius: 50%;
|
||||
animation: dotBounce 1.4s ease-in-out infinite;
|
||||
|
||||
&:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dotBounce {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0.8);
|
||||
opacity: 0.5;
|
||||
}
|
||||
40% {
|
||||
transform: scale(1.2);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -100,10 +100,10 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ dateFormatter(scope.row.createTime) }}
|
||||
{{ formatDateTime(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="120">
|
||||
<el-table-column label="操作" align="center" width="120" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
@ -138,7 +138,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { formatDateTime } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { CellApi } from '@/api/prison/cell'
|
||||
import { AreaApi } from '@/api/prison/area'
|
||||
|
||||
@ -77,7 +77,7 @@
|
||||
<el-divider content-position="left">消费明细</el-divider>
|
||||
<el-table :data="formData.details" border>
|
||||
<el-table-column label="商品名称" prop="goodsName" width="150">
|
||||
<template #default="{ row, $index }">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.goodsName" placeholder="商品名称" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
@ -102,7 +102,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
<template #default="{ row, $index }">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" link @click="removeDetail($index)" :disabled="formData.details.length <= 1">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@ -17,6 +17,15 @@
|
||||
class="!w-140px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯姓名" prop="prisonerName">
|
||||
<el-input
|
||||
v-model="queryParams.prisonerName"
|
||||
placeholder="请输入罪犯姓名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-120px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
@ -89,8 +98,11 @@
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="订单ID" align="center" prop="id" width="80" />
|
||||
<el-table-column label="订单号" align="center" prop="orderNo" width="180" />
|
||||
<el-table-column label="罪犯姓名" align="center" prop="prisonerName" width="100" />
|
||||
<el-table-column label="罪犯编号" align="center" prop="prisonerNo" width="120" />
|
||||
<el-table-column label="监区" align="center" prop="prisonAreaName" width="100" />
|
||||
<el-table-column label="监室" align="center" prop="prisonCellName" width="100" />
|
||||
<el-table-column label="订单号" align="center" prop="orderNo" width="180" />
|
||||
<el-table-column label="类型" align="center" prop="type" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_CONSUMPTION_TYPE" :value="scope.row.type" />
|
||||
@ -113,7 +125,7 @@
|
||||
{{ formatDateTime(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="150">
|
||||
<el-table-column label="操作" align="center" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
@ -176,6 +188,7 @@ const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
prisonerNo: undefined,
|
||||
prisonerName: undefined,
|
||||
type: undefined,
|
||||
status: undefined
|
||||
})
|
||||
|
||||
@ -75,7 +75,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import { StatCard } from './components'
|
||||
import StatCard from './components/StatCard.vue'
|
||||
import { DashboardApi, type DashboardStatisticsVO } from '@/api/prison/dashboard'
|
||||
import EChart from '@/components/Echart/src/Echart.vue'
|
||||
import ChinaMap from './components/ChinaMap.vue'
|
||||
@ -129,6 +129,7 @@ const ageChartOptions = computed<EChartsOption>(() => ({
|
||||
top: 'center',
|
||||
icon: 'circle'
|
||||
},
|
||||
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272'],
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
@ -170,6 +171,7 @@ const sentenceChartOptions = computed<EChartsOption>(() => ({
|
||||
top: 'center',
|
||||
icon: 'circle'
|
||||
},
|
||||
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de'],
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
@ -211,6 +213,7 @@ const educationChartOptions = computed<EChartsOption>(() => ({
|
||||
top: 'center',
|
||||
icon: 'circle'
|
||||
},
|
||||
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4'],
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
|
||||
@ -6,14 +6,16 @@
|
||||
<el-descriptions-item label="罪犯编号">{{ data.prisonerNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ data.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="data.gender" />
|
||||
<dict-tag v-if="data.gender" :type="DICT_TYPE.SYSTEM_USER_SEX" :value="data.gender" />
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">{{ data.idCard }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出生日期">{{ data.birthday }}</el-descriptions-item>
|
||||
<el-descriptions-item label="民族">{{ data.ethnicity }}</el-descriptions-item>
|
||||
<el-descriptions-item label="籍贯" :span="2">{{ data.nativePlace }}</el-descriptions-item>
|
||||
<el-descriptions-item label="文化程度">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_EDUCATION" :value="data.education" />
|
||||
<dict-tag v-if="data.education" :type="DICT_TYPE.PRISON_EDUCATION" :value="data.education" />
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="职业">{{ data.occupation }}</el-descriptions-item>
|
||||
<el-descriptions-item label="家庭住址" :span="2">{{ data.address }}</el-descriptions-item>
|
||||
@ -35,15 +37,18 @@
|
||||
<!-- 监管信息 -->
|
||||
<el-descriptions title="监管信息" :column="2" border style="margin-top: 20px">
|
||||
<el-descriptions-item label="监管等级">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_SUPERVISION_LEVEL" :value="data.supervisionLevel" />
|
||||
<dict-tag v-if="data.supervisionLevel" :type="DICT_TYPE.PRISON_SUPERVISION_LEVEL" :value="data.supervisionLevel" />
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="风险等级">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_RISK_LEVEL" :value="data.riskLevel" />
|
||||
<dict-tag v-if="data.riskLevel" :type="DICT_TYPE.PRISON_RISK_LEVEL" :value="data.riskLevel" />
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当前监区">{{ data.prisonAreaName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="当前监室">{{ data.prisonCellName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<dict-tag :type="DICT_TYPE.PRISONER_STATUS" :value="data.status" />
|
||||
<dict-tag v-if="data.status" :type="DICT_TYPE.PRISONER_STATUS" :value="data.status" />
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ data.remark }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
@ -55,7 +60,7 @@
|
||||
<el-timeline-item
|
||||
v-for="(log, index) in areaHistory"
|
||||
:key="index"
|
||||
:timestamp="log.createTime"
|
||||
:timestamp="formatDateTime(log.createTime)"
|
||||
placement="top"
|
||||
>
|
||||
<el-card>
|
||||
@ -74,7 +79,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import * as PrisonerApi from '@/api/prison/prisoner'
|
||||
import { formatDateTime } from '@/utils/formatTime'
|
||||
import { PrisonerApi } from '@/api/prison/prisoner'
|
||||
|
||||
defineOptions({ name: 'PrisonPrisonerDetail' })
|
||||
|
||||
@ -89,9 +95,9 @@ const open = async (id: number) => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 获取罪犯基本信息
|
||||
data.value = await PrisonerApi.getPrisoner(id)
|
||||
data.value = await PrisonerApi.get(id)
|
||||
// 获取位置历史
|
||||
areaHistory.value = await PrisonerApi.getPrisonerAreaHistory(id)
|
||||
areaHistory.value = await PrisonerApi.getAreaHistory(id)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@ -273,7 +273,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { PrisonerCreateVO } from '@/api/prison/prisoner'
|
||||
import * as PrisonerApi from '@/api/prison/prisoner'
|
||||
import { PrisonerApi } from '@/api/prison/prisoner'
|
||||
import { AreaApi } from '@/api/prison/area'
|
||||
import { CellApi } from '@/api/prison/cell'
|
||||
|
||||
@ -447,7 +447,7 @@ const resetForm = () => {
|
||||
const getPrisonerDetail = async (id: number) => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await PrisonerApi.getPrisoner(id)
|
||||
const data = await PrisonerApi.get(id)
|
||||
formData.value = {
|
||||
...formData.value,
|
||||
...data
|
||||
@ -468,10 +468,10 @@ const submitForm = async () => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
if (dialogType.value === 'create') {
|
||||
await PrisonerApi.createPrisoner(formData.value)
|
||||
await PrisonerApi.create(formData.value)
|
||||
message.success('入监登记成功')
|
||||
} else {
|
||||
await PrisonerApi.updatePrisoner(formData.value)
|
||||
await PrisonerApi.update(formData.value)
|
||||
message.success('修改成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
|
||||
613
src/views/prison/prisoner/PrisonerWorkbench.vue
Normal file
613
src/views/prison/prisoner/PrisonerWorkbench.vue
Normal file
@ -0,0 +1,613 @@
|
||||
<template>
|
||||
<el-drawer v-model="dialogVisible" title="罪犯工作台" size="900px">
|
||||
<div v-loading="loading" class="prisoner-workbench">
|
||||
<!-- 顶部:犯人基本信息卡片 -->
|
||||
<el-card class="info-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>基本信息</span>
|
||||
<el-tag :type="statusType">{{ statusText }}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="4" border size="small">
|
||||
<el-descriptions-item label="罪犯编号">{{ data.prisonerNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ data.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="data.gender" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="监管等级">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_SUPERVISION_LEVEL" :value="data.supervisionLevel" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当前监区">{{ data.prisonAreaName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="当前监室">{{ data.prisonCellName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="入狱日期">{{ formatDateTime(data.imprisonmentDate) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="释放日期">{{ formatDateTime(data.releaseDate) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- Tab页签:相关记录 -->
|
||||
<el-card class="tab-card" shadow="never" style="margin-top: 16px">
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 消费记录Tab -->
|
||||
<el-tab-pane label="消费记录" name="consumption">
|
||||
<div class="tab-header">
|
||||
<span class="stat-item">
|
||||
<span class="label">总消费:</span>
|
||||
<span class="value amount">¥{{ consumptionStats.totalAmount?.toFixed(2) || '0.00' }}</span>
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<span class="label">订单数:</span>
|
||||
<span class="value">{{ consumptionStats.orderCount || 0 }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<el-table :data="consumptionList" stripe style="width: 100%">
|
||||
<el-table-column prop="orderNo" label="订单号" width="180" />
|
||||
<el-table-column prop="type" label="类型" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_CONSUMPTION_TYPE" :value="row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalAmount" label="金额" width="100">
|
||||
<template #default="{ row }">
|
||||
<span :class="{ 'amount': true, 'negative': row.totalAmount < 0 }">
|
||||
¥{{ row.totalAmount?.toFixed(2) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="balance" label="余额" width="120">
|
||||
<template #default="{ row }">
|
||||
<span class="amount">¥{{ row.balance?.toFixed(2) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="tradeTime" label="交易时间" width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.tradeTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_CONSUMPTION_STATUS" :value="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-if="consumptionTotal > 0"
|
||||
v-model:current-page="consumptionPage"
|
||||
v-model:page-size="consumptionPageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="consumptionTotal"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
size="small"
|
||||
@size-change="loadConsumptionList"
|
||||
@current-change="loadConsumptionList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 计分考核Tab -->
|
||||
<el-tab-pane label="计分考核" name="score">
|
||||
<div class="tab-header">
|
||||
<span class="stat-item">
|
||||
<span class="label">累计总分:</span>
|
||||
<span class="value">{{ scoreStats.totalScore || 0 }}</span>
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<span class="label">平均分:</span>
|
||||
<span class="value">{{ scoreStats.avgScore?.toFixed(1) || '0' }}</span>
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<span class="label">考核次数:</span>
|
||||
<span class="value">{{ scoreStats.recordCount || 0 }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<el-table :data="scoreList" stripe style="width: 100%">
|
||||
<el-table-column prop="year" label="年份" width="80" />
|
||||
<el-table-column prop="month" label="月份" width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.year }}-{{ String(row.month).padStart(2, '0') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="baseScore" label="基础分" width="80" />
|
||||
<el-table-column prop="rewardScore" label="加分" width="70">
|
||||
<template #default="{ row }">
|
||||
<span class="positive">+{{ row.rewardScore }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="penaltyScore" label="扣分" width="70">
|
||||
<template #default="{ row }">
|
||||
<span class="negative">-{{ row.penaltyScore }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalScore" label="总分" width="80">
|
||||
<template #default="{ row }">
|
||||
<span class="amount" style="font-weight: bold">{{ row.totalScore }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="level" label="等级" width="80">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_SCORE_LEVEL" :value="row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_SCORE_STATUS" :value="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="assessorName" label="考核人" width="100" />
|
||||
<el-table-column prop="createTime" label="考核时间" width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-if="scoreTotal > 0"
|
||||
v-model:current-page="scorePage"
|
||||
v-model:page-size="scorePageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="scoreTotal"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
size="small"
|
||||
@size-change="loadScoreList"
|
||||
@current-change="loadScoreList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 问卷记录Tab -->
|
||||
<el-tab-pane label="问卷记录" name="questionnaire">
|
||||
<div class="tab-header">
|
||||
<el-button type="primary" size="small" @click="openInitiateDialog">
|
||||
发起问卷测评
|
||||
</el-button>
|
||||
<span class="stat-item">
|
||||
<span class="label">已完成:</span>
|
||||
<span class="value">{{ questionnaireStats.completedCount || 0 }}</span>
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<span class="label">待测评:</span>
|
||||
<span class="value">{{ questionnaireStats.pendingCount || 0 }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<el-table :data="questionnaireList" stripe style="width: 100%">
|
||||
<el-table-column prop="questionnaireName" label="问卷名称" min-width="150" />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_RECORD_STATUS" :value="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalScore" label="得分" width="80">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.totalScore !== undefined" class="amount">{{ row.totalScore }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="passStatus" label="及格状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_RECORD_PASS_STATUS" :value="row.passStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="riskLevel" label="风险等级" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_RISK_LEVEL" :value="row.riskLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="startTime" label="开始时间" width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.startTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="endTime" label="完成时间" width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.endTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="deadline" label="截止日期" width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.deadline) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="row.status === 1"
|
||||
type="success"
|
||||
size="small"
|
||||
link
|
||||
@click="handleStartAssessment(row)"
|
||||
>
|
||||
开始测评
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 1"
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
@click="handleCancelAssessment(row)"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 3 && row.passStatus === 3"
|
||||
type="warning"
|
||||
size="small"
|
||||
link
|
||||
@click="handleManualScore(row)"
|
||||
>
|
||||
人工评分
|
||||
</el-button>
|
||||
<span v-if="row.status !== 1 && !(row.status === 3 && row.passStatus === 3)" class="text-gray">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-if="questionnaireTotal > 0"
|
||||
v-model:current-page="questionnairePage"
|
||||
v-model:page-size="questionnairePageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="questionnaireTotal"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
size="small"
|
||||
@size-change="loadQuestionnaireList"
|
||||
@current-change="loadQuestionnaireList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 风险评估Tab -->
|
||||
<el-tab-pane label="风险评估" name="risk">
|
||||
<div class="tab-header">
|
||||
<span class="stat-item">
|
||||
<span class="label">当前风险等级:</span>
|
||||
<span class="value">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_RISK_LEVEL" :value="data.riskLevel" />
|
||||
</span>
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<span class="label">最近评估:</span>
|
||||
<span class="value">{{ riskStats.lastAssessmentTime || '暂无' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<el-table :data="riskList" stripe style="width: 100%">
|
||||
<el-table-column prop="assessDate" label="评估日期" width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.assessDate) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="riskLevel" label="风险等级" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_RISK_LEVEL" :value="row.riskLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="riskScore" label="风险分值" width="100" />
|
||||
<el-table-column prop="assessmentType" label="评估类型" width="120" />
|
||||
<el-table-column prop="assessorName" label="评估人" width="100" />
|
||||
<el-table-column prop="remark" label="备注" min-width="200" />
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-if="riskTotal > 0"
|
||||
v-model:current-page="riskPage"
|
||||
v-model:page-size="riskPageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="riskTotal"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
size="small"
|
||||
@size-change="loadRiskList"
|
||||
@current-change="loadRiskList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 发起问卷测评弹窗 -->
|
||||
<InitiateAssessmentDialog ref="initiateDialogRef" @success="loadQuestionnaireList" />
|
||||
<!-- 人工评分弹窗 -->
|
||||
<ManualScoreDialog ref="manualScoreDialogRef" @success="loadQuestionnaireList" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, useDictStoreWithOut } from '@/utils/dict'
|
||||
import { dictKeys } from '@/utils/dict'
|
||||
import { formatDateTime } from '@/utils/formatTime'
|
||||
import { PrisonerApi } from '@/api/prison/prisoner'
|
||||
import { ConsumptionApi } from '@/api/prison/consumption'
|
||||
import { ScoreApi } from '@/api/prison/score'
|
||||
import { QuestionnaireRecordApi } from '@/api/prison/questionnairerecord'
|
||||
import InitiateAssessmentDialog from '@/views/prison/questionnairerecord/InitiateAssessmentDialog.vue'
|
||||
import ManualScoreDialog from '@/views/prison/questionnairerecord/ManualScoreDialog.vue'
|
||||
|
||||
defineOptions({ name: 'PrisonerWorkbench' })
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const activeTab = ref('consumption')
|
||||
const data = ref<any>({})
|
||||
|
||||
// 需要加载的字典类型
|
||||
const dictStore = useDictStoreWithOut()
|
||||
const loadDictTypes = async () => {
|
||||
// 触发加载所有字典数据
|
||||
await dictStore.setDictMap()
|
||||
}
|
||||
|
||||
// 消费记录相关
|
||||
const consumptionList = ref([])
|
||||
const consumptionTotal = ref(0)
|
||||
const consumptionPage = ref(1)
|
||||
const consumptionPageSize = ref(10)
|
||||
const consumptionStats = ref<any>({})
|
||||
|
||||
// 计分考核相关
|
||||
const scoreList = ref([])
|
||||
const scoreTotal = ref(0)
|
||||
const scorePage = ref(1)
|
||||
const scorePageSize = ref(10)
|
||||
const scoreStats = ref<any>({})
|
||||
|
||||
// 问卷记录相关
|
||||
const questionnaireList = ref([])
|
||||
const questionnaireTotal = ref(0)
|
||||
const questionnairePage = ref(1)
|
||||
const questionnairePageSize = ref(10)
|
||||
const questionnaireStats = ref<any>({})
|
||||
|
||||
// 风险评估相关
|
||||
const riskList = ref([])
|
||||
const riskTotal = ref(0)
|
||||
const riskPage = ref(1)
|
||||
const riskPageSize = ref(10)
|
||||
const riskStats = ref<any>({})
|
||||
|
||||
// 弹窗组件ref
|
||||
const initiateDialogRef = ref()
|
||||
const manualScoreDialogRef = ref()
|
||||
|
||||
// 状态显示
|
||||
const statusType = computed(() => {
|
||||
const statusMap: Record<number, string> = {
|
||||
1: 'success', // 在狱
|
||||
2: 'warning', // 出狱
|
||||
3: 'info', // 假释
|
||||
4: 'danger' // 释放
|
||||
}
|
||||
return statusMap[data.value.status] || 'info'
|
||||
})
|
||||
|
||||
const statusText = computed(() => {
|
||||
const statusMap: Record<number, string> = {
|
||||
1: '在狱',
|
||||
2: '出狱',
|
||||
3: '假释',
|
||||
4: '释放'
|
||||
}
|
||||
return statusMap[data.value.status] || '未知'
|
||||
})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
loading.value = true
|
||||
try {
|
||||
// 先加载字典数据
|
||||
await loadDictTypes()
|
||||
// 获取罪犯基本信息
|
||||
data.value = await PrisonerApi.get(id)
|
||||
// 重置分页
|
||||
resetPages()
|
||||
// 加载所有数据
|
||||
await Promise.all([
|
||||
loadConsumptionList(),
|
||||
loadScoreList(),
|
||||
loadQuestionnaireList(),
|
||||
loadRiskList()
|
||||
])
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置分页 */
|
||||
const resetPages = () => {
|
||||
consumptionPage.value = 1
|
||||
scorePage.value = 1
|
||||
questionnairePage.value = 1
|
||||
riskPage.value = 1
|
||||
}
|
||||
|
||||
/** 加载消费记录 */
|
||||
const loadConsumptionList = async () => {
|
||||
if (!data.value.prisonerNo) return
|
||||
try {
|
||||
const res = await ConsumptionApi.getConsumptionPage({
|
||||
pageNo: consumptionPage.value,
|
||||
pageSize: consumptionPageSize.value,
|
||||
prisonerNo: data.value.prisonerNo
|
||||
})
|
||||
consumptionList.value = res.list
|
||||
consumptionTotal.value = res.total
|
||||
// 计算统计数据
|
||||
if (res.list.length > 0) {
|
||||
const totalAmount = res.list.reduce((sum: number, item: any) => sum + (item.totalAmount || 0), 0)
|
||||
consumptionStats.value = { totalAmount, orderCount: res.total }
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 加载计分记录 */
|
||||
const loadScoreList = async () => {
|
||||
if (!data.value.prisonerNo) return
|
||||
try {
|
||||
const res = await ScoreApi.getScorePage({
|
||||
pageNo: scorePage.value,
|
||||
pageSize: scorePageSize.value,
|
||||
prisonerNo: data.value.prisonerNo
|
||||
})
|
||||
scoreList.value = res.list
|
||||
scoreTotal.value = res.total
|
||||
// 计算统计数据
|
||||
if (res.list.length > 0) {
|
||||
const totalScore = res.list.reduce((sum: number, item: any) => sum + (item.totalScore || 0), 0)
|
||||
const avgScore = res.list.length > 0 ? totalScore / res.list.length : 0
|
||||
scoreStats.value = { totalScore, avgScore, recordCount: res.total }
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 加载问卷记录 */
|
||||
const loadQuestionnaireList = async () => {
|
||||
if (!data.value.id) return
|
||||
try {
|
||||
const res = await QuestionnaireRecordApi.getQuestionnaireRecordPage({
|
||||
pageNo: questionnairePage.value,
|
||||
pageSize: questionnairePageSize.value,
|
||||
prisonerId: data.value.id
|
||||
})
|
||||
questionnaireList.value = res.list
|
||||
questionnaireTotal.value = res.total
|
||||
// 计算统计数据
|
||||
const completedCount = res.list.filter((item: any) => item.status === 3).length
|
||||
const pendingCount = res.list.filter((item: any) => item.status === 1).length
|
||||
questionnaireStats.value = { completedCount, pendingCount, total: res.total }
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 加载风险评估记录 */
|
||||
const loadRiskList = async () => {
|
||||
if (!data.value.id) return
|
||||
try {
|
||||
// TODO: 等待RiskAssessment模块完成后启用
|
||||
// const res = await RiskAssessmentApi.getRiskAssessmentPage({...})
|
||||
// riskList.value = res.list
|
||||
// riskTotal.value = res.total
|
||||
riskStats.value = { lastAssessmentTime: '暂无数据' }
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 打开发起问卷弹窗 */
|
||||
const openInitiateDialog = () => {
|
||||
initiateDialogRef.value?.open({
|
||||
id: data.value.id,
|
||||
prisonerNo: data.value.prisonerNo,
|
||||
name: data.value.name
|
||||
})
|
||||
}
|
||||
|
||||
/** 开始测评 */
|
||||
const handleStartAssessment = async (row: any) => {
|
||||
try {
|
||||
await QuestionnaireRecordApi.startAssessment(row.id, data.value.id)
|
||||
message.success('测评已开始')
|
||||
loadQuestionnaireList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 取消测评 */
|
||||
const handleCancelAssessment = async (row: any) => {
|
||||
try {
|
||||
await QuestionnaireRecordApi.cancelAssessment(row.id)
|
||||
message.success('测评已取消')
|
||||
loadQuestionnaireList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 人工评分 */
|
||||
const handleManualScore = (row: any) => {
|
||||
manualScoreDialogRef.value?.open(row)
|
||||
}
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.prisoner-workbench {
|
||||
.info-card {
|
||||
:deep(.el-card__header) {
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-card {
|
||||
:deep(.el-card__body) {
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.label {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
|
||||
&.amount {
|
||||
color: #f56c6c;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.positive {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.negative {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-table {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.amount {
|
||||
color: #f56c6c;
|
||||
font-weight: 500;
|
||||
|
||||
&.negative {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
|
||||
.positive {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.negative {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
|
||||
.el-pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.text-gray {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -80,7 +80,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import * as PrisonerApi from '@/api/prison/prisoner'
|
||||
import { PrisonerApi } from '@/api/prison/prisoner'
|
||||
import { AreaApi } from '@/api/prison/area'
|
||||
import { CellApi } from '@/api/prison/cell'
|
||||
|
||||
@ -117,7 +117,7 @@ const open = async (prisonerId: number) => {
|
||||
resetForm()
|
||||
try {
|
||||
// 获取罪犯信息
|
||||
prisoner.value = await PrisonerApi.getPrisoner(prisonerId)
|
||||
prisoner.value = await PrisonerApi.get(prisonerId)
|
||||
formData.prisonerId = prisonerId
|
||||
// 加载监区列表
|
||||
const treeData = await AreaApi.getAreaTree()
|
||||
|
||||
@ -205,8 +205,16 @@
|
||||
<dict-tag :type="DICT_TYPE.PRISONER_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="240">
|
||||
<el-table-column label="操作" align="center" width="300" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handleWorkbench(scope.row)"
|
||||
v-hasPermi="['prison:prisoner:query']"
|
||||
>
|
||||
工作台
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@ -257,20 +265,24 @@
|
||||
<!-- 详情弹窗 -->
|
||||
<PrisonerDetail ref="detailRef" />
|
||||
|
||||
<!-- 工作台弹窗 -->
|
||||
<PrisonerWorkbench ref="workbenchRef" />
|
||||
|
||||
<!-- 调监弹窗 -->
|
||||
<TransferForm ref="transferRef" @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 PrisonerDetail from './PrisonerDetail.vue'
|
||||
import PrisonerWorkbench from './PrisonerWorkbench.vue'
|
||||
import TransferForm from './TransferForm.vue'
|
||||
import download from '@/utils/download'
|
||||
import * as PrisonerApi from '@/api/prison/prisoner'
|
||||
import { AreaApi } from '@/api/prison/area'
|
||||
import { CellApi } from '@/api/prison/cell'
|
||||
import { PrisonerApi } from '@/api/prison/prisoner'
|
||||
import type { PrisonerVO, PrisonerCreateVO } from '@/api/prison/prisoner'
|
||||
import { AreaApi, type AreaNode } from '@/api/prison/area'
|
||||
import { CellApi, type CellVO } from '@/api/prison/cell'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
|
||||
defineOptions({ name: 'PrisonPrisoner' })
|
||||
@ -280,9 +292,9 @@ const message = useMessage() // 消息弹窗
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const areaTreeList = ref([]) // 监区树形列表
|
||||
const cellList = ref([]) // 监室列表
|
||||
const list = ref<PrisonerVO[]>([]) // 列表的数据
|
||||
const areaTreeList = ref<AreaNode[]>([]) // 监区树形列表
|
||||
const cellList = ref<CellVO[]>([]) // 监室列表
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
@ -303,6 +315,7 @@ const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const formRef = ref() // 表单
|
||||
const detailRef = ref() // 详情
|
||||
const workbenchRef = ref() // 工作台
|
||||
const transferRef = ref() // 调监表单
|
||||
|
||||
/** 查询列表 */
|
||||
@ -317,7 +330,7 @@ const getList = async () => {
|
||||
queryParams.imprisonmentDateStart = undefined
|
||||
queryParams.imprisonmentDateEnd = undefined
|
||||
}
|
||||
const data = await PrisonerApi.getPrisonerPage(queryParams)
|
||||
const data = await PrisonerApi.getPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
@ -348,7 +361,7 @@ const handleExport = async () => {
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await PrisonerApi.exportPrisoner(queryParams)
|
||||
const data = await PrisonerApi.export(queryParams)
|
||||
download.excel(data, '罪犯信息.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
@ -362,10 +375,15 @@ const openForm = (type: string, id?: number) => {
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
const handleDetail = (row: any) => {
|
||||
const handleDetail = (row: PrisonerVO) => {
|
||||
detailRef.value.open(row.id)
|
||||
}
|
||||
|
||||
/** 打开工作台 */
|
||||
const handleWorkbench = (row: PrisonerVO) => {
|
||||
workbenchRef.value.open(row.id)
|
||||
}
|
||||
|
||||
/** 打开调监弹窗 */
|
||||
const openTransferForm = () => {
|
||||
if (checkedIds.value.length !== 1) {
|
||||
@ -376,7 +394,7 @@ const openTransferForm = () => {
|
||||
}
|
||||
|
||||
/** 调监操作 */
|
||||
const handleTransfer = (row: any) => {
|
||||
const handleTransfer = (row: PrisonerVO) => {
|
||||
transferRef.value.open(row.id)
|
||||
}
|
||||
|
||||
@ -385,7 +403,7 @@ const handleAreaChange = async (areaId: number) => {
|
||||
queryParams.prisonCellId = undefined
|
||||
cellList.value = []
|
||||
if (areaId) {
|
||||
cellList.value = await CellApi.getCellPage({ areaId, pageNo: 1, pageSize: 200 }).then((res: any) => res.list || [])
|
||||
cellList.value = await CellApi.getCellPage({ areaId, pageNo: 1, pageSize: 200 }).then((res: PageResult<CellVO>) => res.list || [])
|
||||
}
|
||||
}
|
||||
|
||||
@ -401,7 +419,7 @@ const formatIdCard = (idCard: string) => {
|
||||
}
|
||||
|
||||
/** 刑期显示格式化 */
|
||||
const formatSentence = (row: any) => {
|
||||
const formatSentence = (row: PrisonerVO) => {
|
||||
if (row.lifeImprisonment === 1) {
|
||||
return '无期'
|
||||
}
|
||||
@ -422,7 +440,7 @@ const handleDelete = async (id: number) => {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await PrisonerApi.deletePrisoner(id)
|
||||
await PrisonerApi.delete(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
@ -431,7 +449,7 @@ const handleDelete = async (id: number) => {
|
||||
|
||||
/** 批量删除按钮操作 */
|
||||
const checkedIds = ref<number[]>([])
|
||||
const handleRowCheckboxChange = (rows: any[]) => {
|
||||
const handleRowCheckboxChange = (rows: PrisonerVO[]) => {
|
||||
checkedIds.value = rows.map((row) => row.id)
|
||||
}
|
||||
|
||||
@ -440,7 +458,7 @@ const handleDeleteBatch = async () => {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起批量删除
|
||||
await PrisonerApi.deletePrisonerList(checkedIds.value)
|
||||
await PrisonerApi.deleteList(checkedIds.value)
|
||||
checkedIds.value = []
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,236 +0,0 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="所属问卷" prop="questionnaireId">
|
||||
<el-input
|
||||
v-model="queryParams.questionnaireId"
|
||||
placeholder="请输入问卷ID"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="问题标题" prop="title">
|
||||
<el-input
|
||||
v-model="queryParams.title"
|
||||
placeholder="请输入问题标题"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="问题类型" prop="type">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-140px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in questionTypeOptions"
|
||||
: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:question:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
:disabled="checkedIds.length === 0"
|
||||
@click="handleDeleteBatch"
|
||||
v-hasPermi="['prison:question:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:question: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="title" width="200" />
|
||||
<el-table-column label="所属问卷ID" align="center" prop="questionnaireId" width="100" />
|
||||
<el-table-column label="问题类型" align="center" prop="type" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_QUESTION_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分值" align="center" prop="score" width="80" />
|
||||
<el-table-column label="排序" align="center" prop="sort" width="80" />
|
||||
<el-table-column label="是否必答" align="center" prop="isRequired" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.isRequired ? 'danger' : 'success'">
|
||||
{{ scope.row.isRequired ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ dateFormatter(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="150">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['prison:question:update']"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['prison:question: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>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<QuestionForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { QuestionApi, Question } from '@/api/prison/question'
|
||||
import QuestionForm from './QuestionForm.vue'
|
||||
|
||||
defineOptions({ name: 'PrisonQuestion' })
|
||||
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(true)
|
||||
const list = ref<Question[]>([])
|
||||
const total = ref(0)
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
questionnaireId: undefined,
|
||||
title: undefined,
|
||||
type: undefined
|
||||
})
|
||||
const queryFormRef = ref()
|
||||
const exportLoading = ref(false)
|
||||
|
||||
// 问题类型枚举选项 (使用字典)
|
||||
const questionTypeOptions = getIntDictOptions(DICT_TYPE.PRISON_QUESTION_TYPE)
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await QuestionApi.getQuestionPage(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 formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await QuestionApi.deleteQuestion(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除按钮操作 */
|
||||
const checkedIds = ref<number[]>([])
|
||||
const handleRowCheckboxChange = (rows: Question[]) => {
|
||||
checkedIds.value = rows.map((row) => row.id!)
|
||||
}
|
||||
|
||||
const handleDeleteBatch = async () => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await QuestionApi.deleteQuestionList(checkedIds.value)
|
||||
checkedIds.value = []
|
||||
message.success(t('common.delSuccess'))
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
await message.exportConfirm()
|
||||
exportLoading.value = true
|
||||
const data = await QuestionApi.exportQuestion(queryParams)
|
||||
download.excel(data, '问卷问题.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@ -23,12 +23,26 @@
|
||||
<el-form-item label="问卷说明" prop="description">
|
||||
<Editor v-model="formData.description" height="150px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="封面图片" prop="coverImage">
|
||||
<UploadImgs v-model="formData.coverImage" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="填写说明" prop="instruction">
|
||||
<el-input v-model="formData.instruction" type="textarea" placeholder="请输入问卷填写说明" :rows="3" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预计耗时" prop="estimatedTime">
|
||||
<el-input-number v-model="formData.estimatedTime" :min="1" :max="300" placeholder="请输入预计完成时间(分钟)" />
|
||||
<span class="ml-5px">分钟</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="总分" prop="totalScore">
|
||||
<el-input v-model="formData.totalScore" placeholder="请输入总分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="及格分" prop="passScore">
|
||||
<el-input v-model="formData.passScore" placeholder="请输入及格分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否匿名" prop="allowAnonymous">
|
||||
<el-switch v-model="formData.allowAnonymous" />
|
||||
<span class="ml-5px">允许匿名填写</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="formData.status" placeholder="请选择状态">
|
||||
<el-option
|
||||
@ -65,8 +79,12 @@ const formData = ref({
|
||||
title: undefined,
|
||||
type: undefined,
|
||||
description: undefined,
|
||||
coverImage: [] as string[],
|
||||
instruction: undefined,
|
||||
estimatedTime: undefined,
|
||||
totalScore: undefined,
|
||||
passScore: undefined,
|
||||
allowAnonymous: false,
|
||||
status: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
@ -125,8 +143,12 @@ const resetForm = () => {
|
||||
title: undefined,
|
||||
type: undefined,
|
||||
description: undefined,
|
||||
coverImage: [] as string[],
|
||||
instruction: undefined,
|
||||
estimatedTime: undefined,
|
||||
totalScore: undefined,
|
||||
passScore: undefined,
|
||||
allowAnonymous: false,
|
||||
status: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
|
||||
973
src/views/prison/questionnaire/components/QuestionForm.vue
Normal file
973
src/views/prison/questionnaire/components/QuestionForm.vue
Normal file
@ -0,0 +1,973 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="850px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<!-- 使用折叠面板分组 -->
|
||||
<el-collapse v-model="activeCollapse" accordion>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<el-collapse-item title="基本信息" name="basic">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="问题标题" prop="title">
|
||||
<el-input v-model="formData.title" placeholder="请输入问题标题" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="问题类型" prop="type">
|
||||
<el-select v-model="formData.type" placeholder="请选择问题类型" @change="handleTypeChange" style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_QUESTION_TYPE)"
|
||||
: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="partName">
|
||||
<el-select
|
||||
v-model="formData.partName"
|
||||
placeholder="请选择或创建分区"
|
||||
style="width: 100%"
|
||||
allow-create
|
||||
filterable
|
||||
default-first-option
|
||||
>
|
||||
<el-option label="默认分区" value="" />
|
||||
<el-option
|
||||
v-for="part in partitionOptions"
|
||||
:key="part.value"
|
||||
:label="part.label"
|
||||
:value="part.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="是否必答">
|
||||
<el-switch v-model="formData.isRequired" active-text="是" inactive-text="否" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-collapse-item>
|
||||
|
||||
<!-- 选项配置(单选/多选) -->
|
||||
<el-collapse-item title="选项配置" name="options" v-if="formData.type === 1 || formData.type === 2">
|
||||
<!-- 快速粘贴 -->
|
||||
<div class="quick-paste-section">
|
||||
<div class="quick-paste-header">
|
||||
<span>快速导入选项</span>
|
||||
<el-button type="success" size="small" @click="showPasteDialog = true">
|
||||
<Icon icon="ep:document-copy" /> 粘贴导入
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 选项列表 -->
|
||||
<div class="options-container">
|
||||
<div class="options-header">
|
||||
<span class="col-score">分值</span>
|
||||
<span class="col-label">选项文字</span>
|
||||
<span class="col-actions">操作</span>
|
||||
</div>
|
||||
|
||||
<draggable
|
||||
v-model="optionList"
|
||||
item-key="index"
|
||||
handle=".drag-handle"
|
||||
:animation="200"
|
||||
class="option-drag-list"
|
||||
>
|
||||
<template #item="{ element: option, index }">
|
||||
<div class="option-item" :class="{ 'is-other': option.isOther }">
|
||||
<el-icon class="drag-handle"><Rank /></el-icon>
|
||||
<el-input-number
|
||||
v-model="option.score"
|
||||
:min="0"
|
||||
:max="999"
|
||||
size="small"
|
||||
class="col-score-input"
|
||||
/>
|
||||
<template v-if="!option.isOther">
|
||||
<el-input
|
||||
v-model="option.label"
|
||||
placeholder="请输入选项文字"
|
||||
size="small"
|
||||
class="col-label-input"
|
||||
:class="{ 'is-error': option._error }"
|
||||
@blur="validateOption(option)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-tag type="warning" size="small">其他</el-tag>
|
||||
<el-input
|
||||
v-model="option.label"
|
||||
placeholder="提示文字,如:其他,请说明"
|
||||
size="small"
|
||||
class="col-label-input"
|
||||
/>
|
||||
<el-tag type="info" size="small">用户输入</el-tag>
|
||||
</template>
|
||||
<el-button
|
||||
type="danger"
|
||||
:icon="Delete"
|
||||
circle
|
||||
size="small"
|
||||
@click="removeOption(index)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<!-- 添加按钮 -->
|
||||
<div class="add-buttons">
|
||||
<el-button type="primary" plain size="small" :icon="Plus" @click="addOption">
|
||||
添加选项
|
||||
</el-button>
|
||||
<el-button type="warning" plain size="small" :icon="Edit" @click="addOtherOption" :disabled="hasOtherOption">
|
||||
添加"其他"选项
|
||||
</el-button>
|
||||
<el-button type="info" plain size="small" @click="batchSetScore">
|
||||
批量设置分值
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 选项统计 -->
|
||||
<div class="options-stats">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
共 {{ validOptionsCount }} 个有效选项
|
||||
<span v-if="optionList.length < 2 && formData.type !== 3" class="warning-text">
|
||||
(至少需要2个选项)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
|
||||
<!-- 验证设置 -->
|
||||
<el-collapse-item title="验证设置" name="validation">
|
||||
<el-row :gutter="20">
|
||||
<!-- 填空题 -->
|
||||
<template v-if="formData.type === 3">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="占位提示">
|
||||
<el-input v-model="formData.placeholder" placeholder="请输入占位提示文字" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="默认值">
|
||||
<el-input v-model="formData.defaultValue" placeholder="请输入默认值(可选)" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<!-- 评分题 -->
|
||||
<template v-else-if="formData.type === 4">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="最低分">
|
||||
<el-input-number v-model="formData.minValue" :min="0" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="最高分">
|
||||
<el-input-number v-model="formData.maxValue" :min="1" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="默认分值">
|
||||
<el-input-number v-model="formData.score" :min="0" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<!-- 日期题 -->
|
||||
<template v-else-if="formData.type === 5">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="最早日期">
|
||||
<el-date-picker
|
||||
v-model="formData.minValue"
|
||||
type="date"
|
||||
placeholder="选择最早日期(可选)"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="最晚日期">
|
||||
<el-date-picker
|
||||
v-model="formData.maxValue"
|
||||
type="date"
|
||||
placeholder="选择最晚日期(可选)"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<!-- 数字题 -->
|
||||
<template v-else-if="formData.type === 6">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="最小值">
|
||||
<el-input-number v-model="formData.minValue" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="最大值">
|
||||
<el-input-number v-model="formData.maxValue" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="默认值">
|
||||
<el-input-number v-model="formData.defaultValue" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<!-- 其他题型 -->
|
||||
<template v-else>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="分值">
|
||||
<el-input-number v-model="formData.score" :min="0" style="width: 200px" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
|
||||
<!-- 排序字段(始终显示) -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="排序序号">
|
||||
<el-input-number v-model="formData.sort" :min="0" style="width: 100%" />
|
||||
<div class="form-tip">数字越小越靠前</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-collapse-item>
|
||||
|
||||
<!-- 高级设置 -->
|
||||
<el-collapse-item title="高级设置" name="advanced">
|
||||
<!-- 帮助说明 -->
|
||||
<el-form-item label="帮助说明">
|
||||
<el-input
|
||||
v-model="formData.helpText"
|
||||
type="textarea"
|
||||
placeholder="请输入帮助说明文字,将显示在问题下方辅助填写"
|
||||
:rows="2"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 自动填充 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="自动填充">
|
||||
<el-select v-model="formData.autoFillType" style="width: 100%">
|
||||
<el-option label="无" value="NONE" />
|
||||
<el-option label="系统自动" value="AUTO" />
|
||||
<el-option label="手动输入" value="MANUAL" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="填充来源" v-if="formData.autoFillType !== 'NONE'">
|
||||
<el-select v-model="formData.autoFillSource" placeholder="请选择填充来源" style="width: 100%" clearable>
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.PRISON_QUESTION_AUTO_FILL_SOURCE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 显示条件 -->
|
||||
<el-form-item label="显示条件">
|
||||
<div class="condition-builder">
|
||||
<div class="condition-row">
|
||||
<span class="condition-text">当</span>
|
||||
<el-select v-model="conditionForm.field" placeholder="选择字段" style="width: 140px" clearable>
|
||||
<el-option label="风险等级" value="riskLevel" />
|
||||
<el-option label="评估次数" value="assessmentCount" />
|
||||
<el-option label="总分" value="totalScore" />
|
||||
<el-option label="年龄" value="age" />
|
||||
<el-option label="在押时长(月)" value="months" />
|
||||
</el-select>
|
||||
<el-select v-model="conditionForm.operator" placeholder="运算符" style="width: 100px">
|
||||
<el-option label="等于" value="=" />
|
||||
<el-option label="不等于" value="!=" />
|
||||
<el-option label="大于" value=">" />
|
||||
<el-option label="大于等于" value=">=" />
|
||||
<el-option label="小于" value="<" />
|
||||
<el-option label="小于等于" value="<=" />
|
||||
</el-select>
|
||||
<el-input v-model="conditionForm.value" placeholder="输入值" style="width: 120px" />
|
||||
<el-switch
|
||||
v-model="conditionForm.enabled"
|
||||
active-text="启用"
|
||||
inactive-text="禁用"
|
||||
:disabled="!canEnableCondition"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="conditionForm.enabled" class="condition-preview">
|
||||
预览:{{ getConditionPreview() }}
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
|
||||
</el-collapse>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<!-- 快速粘贴弹窗 -->
|
||||
<Dialog title="快速导入选项" v-model="showPasteDialog" width="550px">
|
||||
<el-input
|
||||
v-model="pasteText"
|
||||
type="textarea"
|
||||
:rows="10"
|
||||
placeholder="请粘贴选项内容,支持以下分隔方式:
|
||||
• 每行一个选项
|
||||
• 逗号分隔:选项1,选项2,选项3
|
||||
• 分号分隔:选项1;选项2;选项3
|
||||
• 顿号分隔:选项1、选项2、选项3"
|
||||
/>
|
||||
<div class="paste-preview">
|
||||
<div class="preview-header">
|
||||
<span>预览(识别到 {{ parsedOptions.length }} 个选项)</span>
|
||||
<el-button v-if="parsedOptions.length > 0" type="primary" link @click="pasteText = ''">
|
||||
清空
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="preview-tags">
|
||||
<el-tag
|
||||
v-for="(opt, idx) in parsedOptions"
|
||||
:key="idx"
|
||||
type="info"
|
||||
size="small"
|
||||
effect="plain"
|
||||
>
|
||||
{{ opt }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="showPasteDialog = false">取 消</el-button>
|
||||
<el-button
|
||||
@click="confirmPaste"
|
||||
type="primary"
|
||||
:disabled="parsedOptions.length === 0"
|
||||
>
|
||||
确 定(导入 {{ parsedOptions.length }} 个)
|
||||
</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<!-- 批量设置分值弹窗 -->
|
||||
<Dialog title="批量设置分值" v-model="showBatchDialog" width="400px">
|
||||
<el-form>
|
||||
<el-form-item label="分值">
|
||||
<el-input-number v-model="batchScore" :min="0" :max="999" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showBatchDialog = false">取 消</el-button>
|
||||
<el-button @click="applyBatchScore" type="primary">确 定</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
|
||||
import { QuestionApi, Question } from '@/api/prison/question'
|
||||
import { Plus, Delete, Edit, InfoFilled, Rank } from '@element-plus/icons-vue'
|
||||
import draggable from 'vuedraggable'
|
||||
|
||||
defineOptions({ name: 'QuestionForm' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
const formLoading = ref(false)
|
||||
const formType = ref('')
|
||||
const activeCollapse = ref('basic') // 默认展开基本信息
|
||||
|
||||
// 分区选项
|
||||
const partitionOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
|
||||
// 快速粘贴
|
||||
const showPasteDialog = ref(false)
|
||||
const pasteText = ref('')
|
||||
|
||||
// 批量设置
|
||||
const showBatchDialog = ref(false)
|
||||
const batchScore = ref(0)
|
||||
|
||||
// 条件表单
|
||||
const conditionForm = reactive({
|
||||
enabled: false,
|
||||
field: '',
|
||||
operator: '=',
|
||||
value: ''
|
||||
})
|
||||
|
||||
// 选项接口
|
||||
interface OptionItem {
|
||||
label: string
|
||||
score: number
|
||||
isOther?: boolean
|
||||
_error?: boolean // 验证错误标记
|
||||
}
|
||||
|
||||
// 计算属性:是否可以启用条件
|
||||
const canEnableCondition = computed(() => {
|
||||
return conditionForm.field && conditionForm.operator && conditionForm.value
|
||||
})
|
||||
|
||||
// 选项列表
|
||||
const optionList = ref<OptionItem[]>([])
|
||||
|
||||
// 是否有"其他"选项
|
||||
const hasOtherOption = computed(() => {
|
||||
return optionList.value.some(o => o.isOther)
|
||||
})
|
||||
|
||||
// 有效选项数量
|
||||
const validOptionsCount = computed(() => {
|
||||
return optionList.value.filter(o => o.label.trim()).length
|
||||
})
|
||||
|
||||
const formData = ref({
|
||||
id: undefined as number | undefined,
|
||||
questionnaireId: undefined as number | undefined,
|
||||
title: undefined as string | undefined,
|
||||
type: undefined as number | undefined,
|
||||
options: undefined as string | undefined,
|
||||
score: undefined as number | undefined,
|
||||
sort: undefined as number | undefined,
|
||||
isRequired: true as boolean,
|
||||
partName: undefined as string | undefined,
|
||||
helpText: undefined as string | undefined,
|
||||
placeholder: undefined as string | undefined,
|
||||
defaultValue: undefined as string | undefined,
|
||||
autoFillType: 'NONE' as string,
|
||||
autoFillSource: undefined as string | undefined,
|
||||
displayCondition: undefined as string | undefined,
|
||||
minValue: undefined as number | undefined,
|
||||
maxValue: undefined as number | undefined
|
||||
})
|
||||
|
||||
const formRules = reactive({
|
||||
title: [{ required: true, message: '问题标题不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '请选择问题类型', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref()
|
||||
|
||||
/** 添加选项 */
|
||||
const addOption = () => {
|
||||
optionList.value.push({ label: '', score: 0, isOther: false })
|
||||
}
|
||||
|
||||
/** 添加"其他"选项 */
|
||||
const addOtherOption = () => {
|
||||
if (hasOtherOption.value) {
|
||||
message.warning('已存在"其他"选项')
|
||||
return
|
||||
}
|
||||
optionList.value.push({ label: '其他,请说明', score: 0, isOther: true })
|
||||
}
|
||||
|
||||
/** 删除选项 */
|
||||
const removeOption = (index: number) => {
|
||||
optionList.value.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 验证选项 */
|
||||
const validateOption = (option: OptionItem) => {
|
||||
option._error = !option.label.trim()
|
||||
}
|
||||
|
||||
/** 批量设置分值 */
|
||||
const batchSetScore = () => {
|
||||
batchScore.value = 0
|
||||
showBatchDialog.value = true
|
||||
}
|
||||
|
||||
/** 应用批量分值 */
|
||||
const applyBatchScore = () => {
|
||||
optionList.value.forEach(o => {
|
||||
if (!o.isOther) {
|
||||
o.score = batchScore.value
|
||||
}
|
||||
})
|
||||
showBatchDialog.value = false
|
||||
message.success(`已将所有选项分值设为 ${batchScore.value}`)
|
||||
}
|
||||
|
||||
/** 问题类型变化 */
|
||||
const handleTypeChange = (val: number) => {
|
||||
optionList.value = []
|
||||
// 重置默认值
|
||||
if (val === 4) { // 评分题
|
||||
formData.value.minValue = 1
|
||||
formData.value.maxValue = 5
|
||||
formData.value.score = 5
|
||||
} else if (val === 6) { // 数字题
|
||||
formData.value.minValue = 0
|
||||
formData.value.maxValue = 100
|
||||
formData.value.score = 0
|
||||
} else if (val === 5) { // 日期题
|
||||
formData.value.minValue = undefined
|
||||
formData.value.maxValue = undefined
|
||||
} else {
|
||||
formData.value.minValue = undefined
|
||||
formData.value.maxValue = undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** 解析选项 JSON */
|
||||
const parseOptions = (optionsStr: string | undefined): OptionItem[] => {
|
||||
if (!optionsStr) return []
|
||||
try {
|
||||
return JSON.parse(optionsStr)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/** 将选项转为 JSON */
|
||||
const stringifyOptions = (options: OptionItem[]): string => {
|
||||
if (options.length === 0) return ''
|
||||
return JSON.stringify(options.filter(o => o.label.trim()))
|
||||
}
|
||||
|
||||
/** 解析粘贴文本 */
|
||||
const parsedOptions = computed(() => {
|
||||
if (!pasteText.value.trim()) return []
|
||||
const text = pasteText.value.trim()
|
||||
let items: string[]
|
||||
if (text.includes('\n')) {
|
||||
items = text.split('\n').map(s => s.trim()).filter(s => s)
|
||||
} else if (text.includes(',') || text.includes(',')) {
|
||||
items = text.split(/[,,]/).map(s => s.trim()).filter(s => s)
|
||||
} else if (text.includes(';') || text.includes(';')) {
|
||||
items = text.split(/[;;]/).map(s => s.trim()).filter(s => s)
|
||||
} else if (text.includes('、')) {
|
||||
items = text.split('、').map(s => s.trim()).filter(s => s)
|
||||
} else {
|
||||
items = text.split(/\s+/).map(s => s.trim()).filter(s => s)
|
||||
}
|
||||
return [...new Set(items)]
|
||||
})
|
||||
|
||||
/** 确认粘贴 */
|
||||
const confirmPaste = () => {
|
||||
if (parsedOptions.value.length === 0) return
|
||||
parsedOptions.value.forEach(label => {
|
||||
optionList.value.push({ label, score: 0, isOther: false })
|
||||
})
|
||||
pasteText.value = ''
|
||||
showPasteDialog.value = false
|
||||
message.success(`已添加 ${parsedOptions.value.length} 个选项`)
|
||||
}
|
||||
|
||||
/** 条件预览 */
|
||||
const getConditionPreview = () => {
|
||||
const fieldLabels: Record<string, string> = {
|
||||
riskLevel: '风险等级',
|
||||
assessmentCount: '评估次数',
|
||||
totalScore: '总分',
|
||||
age: '年龄',
|
||||
months: '在押时长'
|
||||
}
|
||||
const opLabels: Record<string, string> = {
|
||||
'=': '等于', '!=': '不等于', '>': '大于',
|
||||
'>=': '大于等于', '<': '小于', '<=': '小于等于'
|
||||
}
|
||||
return `当${fieldLabels[conditionForm.field] || conditionForm.field} ${opLabels[conditionForm.operator]} ${conditionForm.value}时显示`
|
||||
}
|
||||
|
||||
/** 更新显示条件 */
|
||||
const updateDisplayCondition = () => {
|
||||
if (conditionForm.enabled && conditionForm.field && conditionForm.operator && conditionForm.value) {
|
||||
formData.value.displayCondition = JSON.stringify({
|
||||
field: conditionForm.field,
|
||||
operator: conditionForm.operator,
|
||||
value: conditionForm.value
|
||||
})
|
||||
} else {
|
||||
formData.value.displayCondition = undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听条件启用状态 */
|
||||
watch(() => conditionForm.enabled, (val) => {
|
||||
if (val) {
|
||||
updateDisplayCondition()
|
||||
} else {
|
||||
formData.value.displayCondition = undefined
|
||||
}
|
||||
})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number, questionnaireId?: number, partitions?: Array<{ label: string; value: string }>) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
activeCollapse.value = 'basic' // 重置展开状态
|
||||
resetForm()
|
||||
|
||||
if (partitions) {
|
||||
partitionOptions.value = partitions
|
||||
} else {
|
||||
partitionOptions.value = []
|
||||
}
|
||||
|
||||
if (questionnaireId) {
|
||||
formData.value.questionnaireId = questionnaireId
|
||||
}
|
||||
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await QuestionApi.getQuestion(id)
|
||||
formData.value = { ...data } as any
|
||||
if (data.type === 1 || data.type === 2) {
|
||||
optionList.value = parseOptions(data.options)
|
||||
}
|
||||
// 解析显示条件
|
||||
if (data.displayCondition) {
|
||||
try {
|
||||
const obj = JSON.parse(data.displayCondition)
|
||||
conditionForm.enabled = true
|
||||
conditionForm.field = obj.field || ''
|
||||
conditionForm.operator = obj.operator || '='
|
||||
conditionForm.value = obj.value || ''
|
||||
} catch {}
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open })
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success'])
|
||||
const submitForm = async () => {
|
||||
await formRef.value.validate()
|
||||
|
||||
// 验证选项
|
||||
if (formData.value.type === 1 || formData.value.type === 2) {
|
||||
const normalOptions = optionList.value.filter(o => !o.isOther)
|
||||
const otherOption = optionList.value.find(o => o.isOther)
|
||||
|
||||
// 检查选项数量
|
||||
if (otherOption) {
|
||||
if (normalOptions.length < 1) {
|
||||
message.error('"其他"选项外至少需要1个普通选项')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if (normalOptions.length < 2) {
|
||||
message.error('请至少添加2个选项')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 检查空选项
|
||||
const emptyOption = normalOptions.find(o => !o.label.trim())
|
||||
if (emptyOption) {
|
||||
message.error('请完善所有选项的文字')
|
||||
return
|
||||
}
|
||||
|
||||
if (otherOption && !otherOption.label.trim()) {
|
||||
message.error('请输入"其他"选项的提示文字')
|
||||
return
|
||||
}
|
||||
|
||||
formData.value.options = stringifyOptions(optionList.value)
|
||||
}
|
||||
|
||||
// 处理日期格式
|
||||
if (formData.value.type === 5) {
|
||||
formData.value.options = JSON.stringify({
|
||||
min: formData.value.minValue || '',
|
||||
max: formData.value.maxValue || ''
|
||||
})
|
||||
}
|
||||
|
||||
// 处理数字范围
|
||||
if (formData.value.type === 6) {
|
||||
formData.value.options = JSON.stringify({
|
||||
min: formData.value.minValue ?? '',
|
||||
max: formData.value.maxValue ?? ''
|
||||
})
|
||||
}
|
||||
|
||||
// 处理评分范围
|
||||
if (formData.value.type === 4) {
|
||||
formData.value.options = JSON.stringify({
|
||||
min: formData.value.minValue ?? 1,
|
||||
max: formData.value.maxValue ?? 5
|
||||
})
|
||||
}
|
||||
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = { ...formData.value } as any
|
||||
if (formType.value === 'create') {
|
||||
await QuestionApi.createQuestion(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await QuestionApi.updateQuestion(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
questionnaireId: formData.value.questionnaireId,
|
||||
title: undefined,
|
||||
type: undefined,
|
||||
options: undefined,
|
||||
score: undefined,
|
||||
sort: optionList.value.length, // 默认排序为当前数量
|
||||
isRequired: true,
|
||||
partName: undefined,
|
||||
helpText: undefined,
|
||||
placeholder: undefined,
|
||||
defaultValue: undefined,
|
||||
autoFillType: 'NONE',
|
||||
autoFillSource: undefined,
|
||||
displayCondition: undefined,
|
||||
minValue: undefined,
|
||||
maxValue: undefined
|
||||
}
|
||||
optionList.value = []
|
||||
conditionForm.enabled = false
|
||||
conditionForm.field = ''
|
||||
conditionForm.operator = '='
|
||||
conditionForm.value = ''
|
||||
pasteText.value = ''
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 折叠面板样式 */
|
||||
:deep(.el-collapse-item__header) {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
:deep(.el-collapse-item__content) {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 快速粘贴区域 */
|
||||
.quick-paste-section {
|
||||
margin-bottom: 16px;
|
||||
padding: 12px 16px;
|
||||
background: linear-gradient(135deg, #f0f9eb 0%, #e8f5e9 100%);
|
||||
border: 1px solid #c2e7b0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.quick-paste-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
/* 选项容器 */
|
||||
.options-container {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.options-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.col-score {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.col-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.col-actions {
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.option-drag-list {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
margin-bottom: 8px;
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.option-item:hover {
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.option-item.is-other {
|
||||
background: #fffbf0;
|
||||
border-color: #e6a23c;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
cursor: grab;
|
||||
color: #c0c4cc;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.col-score-input {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.col-label-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.col-label-input.is-error {
|
||||
--el-input-border-color: #f56c6c;
|
||||
}
|
||||
|
||||
.add-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px dashed #e4e7ed;
|
||||
}
|
||||
|
||||
.options-stats {
|
||||
margin-top: 12px;
|
||||
padding: 8px 12px;
|
||||
background: #f4f4f5;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
color: #e6a23c;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 条件构建器 */
|
||||
.condition-builder {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.condition-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.condition-text {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.condition-preview {
|
||||
margin-top: 12px;
|
||||
padding: 10px 14px;
|
||||
background: #ecf5ff;
|
||||
border: 1px solid #b3d8ff;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
color: #409eff;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.paste-preview {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.preview-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@ -193,11 +193,11 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain :icon="Plus" @click="addPartition">添加分区</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">保存设置</el-button>
|
||||
<el-button @click="savePartitions" type="primary" v-hasPermi="['prison:question:update']">保存设置</el-button>
|
||||
<el-button @click="partDialogVisible = false">取消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
@ -206,7 +206,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { QuestionApi, Question } from '@/api/prison/question'
|
||||
import QuestionForm from '../../question/QuestionForm.vue'
|
||||
import QuestionForm from './QuestionForm.vue'
|
||||
import { Folder, InfoFilled, Rank, Plus, Delete } from '@element-plus/icons-vue'
|
||||
import draggable from 'vuedraggable'
|
||||
|
||||
|
||||
@ -110,10 +110,10 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ dateFormatter(scope.row.createTime) }}
|
||||
{{ formatDateTime(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="150">
|
||||
<el-table-column label="操作" align="center" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
@ -157,7 +157,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { formatDateTime } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { QuestionnaireApi, Questionnaire } from '@/api/prison/questionnaire'
|
||||
import QuestionnaireForm from './QuestionnaireForm.vue'
|
||||
|
||||
@ -21,7 +21,13 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择罪犯" prop="prisonerIds">
|
||||
<!-- 预选罪犯模式(只读显示) -->
|
||||
<el-form-item v-if="preselectedPrisoner" label="选择罪犯">
|
||||
<el-tag>{{ preselectedPrisoner.prisonerNo }} - {{ preselectedPrisoner.name }}</el-tag>
|
||||
<span class="text-tip ml-10px">(已选定)</span>
|
||||
</el-form-item>
|
||||
<!-- 正常选择模式 -->
|
||||
<el-form-item v-else label="选择罪犯" prop="prisonerIds">
|
||||
<el-select
|
||||
v-model="formData.prisonerIds"
|
||||
placeholder="请选择罪犯"
|
||||
@ -93,6 +99,9 @@ const formData = reactive({
|
||||
remark: ''
|
||||
})
|
||||
|
||||
// 预选罪犯信息
|
||||
const preselectedPrisoner = ref<{ id: number; prisonerNo: string; name: string } | null>(null)
|
||||
|
||||
const rules = {
|
||||
questionnaireId: [{ required: true, message: '请选择问卷', trigger: 'change' }],
|
||||
prisonerIds: [{ required: true, message: '请选择罪犯', trigger: 'change' }]
|
||||
@ -101,7 +110,7 @@ const rules = {
|
||||
/** 获取问卷列表 */
|
||||
const getQuestionnaireList = async () => {
|
||||
try {
|
||||
const data = await QuestionnaireApi.getQuestionnairePage({ pageNo: 1, pageSize: 1000 })
|
||||
const data = await QuestionnaireApi.getQuestionnairePage({ pageNo: 1, pageSize: 200 })
|
||||
questionnaireList.value = data.list
|
||||
} catch {}
|
||||
}
|
||||
@ -127,19 +136,32 @@ const searchPrisoners = async (keyword: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = () => {
|
||||
/** 打开弹窗
|
||||
* @param prisoner 可选的预选罪犯信息(从工作台打开时传入)
|
||||
*/
|
||||
const open = (prisoner?: { id: number; prisonerNo: string; name: string }) => {
|
||||
dialogVisible.value = true
|
||||
title.value = '发起测评'
|
||||
// 重置表单
|
||||
formData.questionnaireId = undefined
|
||||
formData.prisonerIds = []
|
||||
formData.deadline = ''
|
||||
formData.remark = ''
|
||||
|
||||
// 设置预选罪犯
|
||||
if (prisoner) {
|
||||
preselectedPrisoner.value = prisoner
|
||||
formData.prisonerIds = [prisoner.id]
|
||||
} else {
|
||||
preselectedPrisoner.value = null
|
||||
formData.prisonerIds = []
|
||||
}
|
||||
|
||||
// 加载问卷列表
|
||||
getQuestionnaireList()
|
||||
// 清空罪犯列表,等待用户搜索
|
||||
prisonerList.value = []
|
||||
// 如果没有预选罪犯,清空罪犯列表,等待用户搜索
|
||||
if (!prisoner) {
|
||||
prisonerList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交 */
|
||||
|
||||
@ -153,10 +153,13 @@
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="160"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" min-width="200px">
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ formatDateTime(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" min-width="200px" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.status === 1"
|
||||
@ -235,7 +238,7 @@
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { formatDateTime } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { QuestionnaireRecordApi, QuestionnaireRecord, QuestionnaireRecordPageParams } from '@/api/prison/questionnairerecord'
|
||||
import { QuestionnaireApi } from '@/api/prison/questionnaire'
|
||||
@ -286,7 +289,7 @@ const getList = async () => {
|
||||
/** 获取问卷列表 */
|
||||
const getQuestionnaireList = async () => {
|
||||
try {
|
||||
const data = await QuestionnaireApi.getQuestionnairePage({ pageNo: 1, pageSize: 1000 })
|
||||
const data = await QuestionnaireApi.getQuestionnairePage({ pageNo: 1, pageSize: 200 })
|
||||
questionnaireList.value = data.list
|
||||
} catch {}
|
||||
}
|
||||
|
||||
113
src/views/prison/quick-comment/ImportDialog.vue
Normal file
113
src/views/prison/quick-comment/ImportDialog.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="导入评语"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
|
||||
<el-form-item label="评语分类" prop="categoryId">
|
||||
<el-select
|
||||
v-model="formData.categoryId"
|
||||
placeholder="请选择分类"
|
||||
class="!w-100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in categoryOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="评语内容" prop="contents">
|
||||
<el-input
|
||||
v-model="formData.contentsStr"
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
placeholder="每行一条评语"
|
||||
@blur="handleContentsChange"
|
||||
/>
|
||||
<div class="tip">每行输入一条评语,支持批量导入</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="handleSubmit">
|
||||
导入
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { QuickCommentApi } from '@/api/prison/quick-comment'
|
||||
|
||||
defineOptions({ name: 'ImportDialog' })
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const formRef = ref()
|
||||
const categoryOptions = ref<{ id: number; name: string }[]>([])
|
||||
|
||||
const formData = reactive({
|
||||
categoryId: undefined as number | undefined,
|
||||
contents: [] as string[],
|
||||
contentsStr: ''
|
||||
})
|
||||
|
||||
const rules = {
|
||||
categoryId: [{ required: true, message: '请选择评语分类', trigger: 'change' }],
|
||||
contents: [{ required: true, message: '请输入评语内容', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
/** 处理内容变化 */
|
||||
const handleContentsChange = () => {
|
||||
formData.contents = formData.contentsStr
|
||||
.split('\n')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0)
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = (options: { id: number; name: string }[]) => {
|
||||
dialogVisible.value = true
|
||||
categoryOptions.value = options
|
||||
formData.categoryId = undefined
|
||||
formData.contentsStr = ''
|
||||
formData.contents = []
|
||||
}
|
||||
|
||||
/** 提交 */
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate()
|
||||
handleContentsChange()
|
||||
if (formData.contents.length === 0) {
|
||||
return message.warning('请输入评语内容')
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
await QuickCommentApi.importComments({
|
||||
categoryId: formData.categoryId!,
|
||||
contents: formData.contents
|
||||
})
|
||||
message.success(`成功导入 ${formData.contents.length} 条评语`)
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
131
src/views/prison/quick-comment/QuickCommentForm.vue
Normal file
131
src/views/prison/quick-comment/QuickCommentForm.vue
Normal file
@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="isEdit ? '编辑评语' : '新增评语'"
|
||||
width="600px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="评语分类" prop="categoryId">
|
||||
<el-select
|
||||
v-model="formData.categoryId"
|
||||
placeholder="请选择分类"
|
||||
class="!w-100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in categoryOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="评语内容" prop="content">
|
||||
<el-input
|
||||
v-model="formData.content"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
placeholder="请输入评语内容"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="formData.sort" :min="0" :max="9999" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :value="1">启用</el-radio>
|
||||
<el-radio :value="0">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="handleSubmit">
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { QuickCommentApi, QuickComment, CommentCategoryApi } from '@/api/prison/quick-comment'
|
||||
|
||||
defineOptions({ name: 'QuickCommentForm' })
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const formRef = ref()
|
||||
const categoryOptions = ref<{ id: number; name: string }[]>([])
|
||||
|
||||
const formData = reactive({
|
||||
id: undefined as number | undefined,
|
||||
categoryId: undefined as number | undefined,
|
||||
content: '',
|
||||
sort: 0,
|
||||
status: 1
|
||||
})
|
||||
|
||||
const rules = {
|
||||
categoryId: [{ required: true, message: '请选择评语分类', trigger: 'change' }],
|
||||
content: [{ required: true, message: '请输入评语内容', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
}
|
||||
|
||||
/** 获取分类列表 */
|
||||
const getCategoryList = async () => {
|
||||
const data = await CommentCategoryApi.getList({ status: 1 })
|
||||
categoryOptions.value = data.map((item: any) => ({
|
||||
id: item.id,
|
||||
name: item.name
|
||||
}))
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id?: number) => {
|
||||
dialogVisible.value = true
|
||||
isEdit.value = !!id
|
||||
formData.id = id
|
||||
formData.categoryId = undefined
|
||||
formData.content = ''
|
||||
formData.sort = 0
|
||||
formData.status = 1
|
||||
await getCategoryList()
|
||||
if (id) {
|
||||
const data = await QuickCommentApi.get(id)
|
||||
formData.categoryId = data.categoryId
|
||||
formData.content = data.content
|
||||
formData.sort = data.sort
|
||||
formData.status = data.status
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交 */
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate()
|
||||
loading.value = true
|
||||
try {
|
||||
if (isEdit.value) {
|
||||
await QuickCommentApi.update(formData)
|
||||
} else {
|
||||
await QuickCommentApi.create(formData)
|
||||
}
|
||||
message.success(isEdit.value ? '编辑成功' : '新增成功')
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
264
src/views/prison/quick-comment/index.vue
Normal file
264
src/views/prison/quick-comment/index.vue
Normal file
@ -0,0 +1,264 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="评语分类" prop="categoryId">
|
||||
<el-select
|
||||
v-model="queryParams.categoryId"
|
||||
placeholder="请选择分类"
|
||||
clearable
|
||||
class="!w-160px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in categoryOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="评语内容" prop="content">
|
||||
<el-input
|
||||
v-model="queryParams.content"
|
||||
placeholder="请输入评语内容"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-100px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_COMMON_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="handleCreate"
|
||||
v-hasPermi="['prison:quick-comment:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增评语
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleImport"
|
||||
v-hasPermi="['prison:quick-comment:import']"
|
||||
>
|
||||
<Icon icon="ep:upload" class="mr-5px" /> 导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:quick-comment:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 评语列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="分类" prop="categoryName" width="120" align="center" />
|
||||
<el-table-column label="评语内容" prop="content" min-width="400" show-overflow-tooltip />
|
||||
<el-table-column label="使用次数" prop="usageCount" width="100" align="center" />
|
||||
<el-table-column label="排序" prop="sort" width="80" align="center" />
|
||||
<el-table-column label="状态" prop="status" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="createTime" width="180" align="center" />
|
||||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleUpdate(row)"
|
||||
v-hasPermi="['prison:quick-comment:update']"
|
||||
>
|
||||
<Icon icon="ep:edit" /> 编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
v-hasPermi="['prison:quick-comment:delete']"
|
||||
>
|
||||
<Icon icon="ep: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>
|
||||
|
||||
<!-- 评语表单弹窗 -->
|
||||
<QuickCommentForm ref="formRef" @success="getList" />
|
||||
|
||||
<!-- 导入弹窗 -->
|
||||
<ImportDialog ref="importDialogRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import download from '@/utils/download'
|
||||
import { QuickCommentApi, QuickCommentPageParams, CommentCategoryApi, QuickComment } from '@/api/prison/quick-comment'
|
||||
import QuickCommentForm from './QuickCommentForm.vue'
|
||||
import ImportDialog from './ImportDialog.vue'
|
||||
|
||||
defineOptions({ name: 'PrisonQuickComment' })
|
||||
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const list = ref<QuickComment[]>([])
|
||||
const total = ref(0)
|
||||
const ids = ref<number[]>([])
|
||||
const categoryOptions = ref<{ id: number; name: string }[]>([])
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
categoryId: undefined as number | undefined,
|
||||
content: undefined as string | undefined,
|
||||
status: undefined as number | undefined
|
||||
})
|
||||
const queryFormRef = ref()
|
||||
|
||||
/** 获取评语列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await QuickCommentApi.getPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取分类列表 */
|
||||
const getCategoryList = async () => {
|
||||
const data = await CommentCategoryApi.getList({ status: 1 })
|
||||
categoryOptions.value = data.map((item: CommentCategoryApi) => ({
|
||||
id: item.id,
|
||||
name: item.name
|
||||
}))
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: QuickComment[]) => {
|
||||
ids.value = selection.map((item) => item.id)
|
||||
}
|
||||
|
||||
/** 新增评语 */
|
||||
const formRef = ref()
|
||||
const handleCreate = () => {
|
||||
formRef.value?.open()
|
||||
}
|
||||
|
||||
/** 编辑评语 */
|
||||
const handleUpdate = (row: QuickComment) => {
|
||||
formRef.value?.open(row.id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await QuickCommentApi.delete(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除 */
|
||||
const handleBatchDelete = async () => {
|
||||
if (ids.value.length === 0) {
|
||||
return message.warning('请选择要删除的数据')
|
||||
}
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await QuickCommentApi.deleteList(ids.value)
|
||||
message.success(t('common.delSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导入评语 */
|
||||
const importDialogRef = ref()
|
||||
const handleImport = () => {
|
||||
importDialogRef.value?.open(categoryOptions.value)
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
await message.exportConfirm()
|
||||
exportLoading.value = true
|
||||
const data = await QuickCommentApi.export(queryParams)
|
||||
download.excel(data, '快捷评语.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
getCategoryList()
|
||||
})
|
||||
</script>
|
||||
@ -70,7 +70,7 @@
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>释放登记列表</span>
|
||||
<el-button type="primary" @click="handleCreate" v-hasPermi="'prison:release:create'">
|
||||
<el-button type="primary" @click="handleCreate" v-hasPermi="['prison:release:create']">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增释放登记
|
||||
</el-button>
|
||||
</div>
|
||||
@ -95,7 +95,7 @@
|
||||
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="handleDetail(row)" v-hasPermi="'prison:release:query'">
|
||||
<el-button link type="primary" @click="handleDetail(row)" v-hasPermi="['prison:release:query']">
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
@ -103,7 +103,7 @@
|
||||
type="primary"
|
||||
@click="handleEdit(row)"
|
||||
v-if="row.status === 1"
|
||||
v-hasPermi="'prison:release:update'"
|
||||
v-hasPermi="['prison:release:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
@ -112,7 +112,7 @@
|
||||
type="success"
|
||||
@click="handleDoRelease(row)"
|
||||
v-if="row.status === 1"
|
||||
v-hasPermi="'prison:release:update'"
|
||||
v-hasPermi="['prison:release:update']"
|
||||
>
|
||||
执行释放
|
||||
</el-button>
|
||||
@ -121,7 +121,7 @@
|
||||
type="warning"
|
||||
@click="handleCancel(row)"
|
||||
v-if="row.status === 1"
|
||||
v-hasPermi="'prison:release:update'"
|
||||
v-hasPermi="['prison:release:update']"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
@ -130,7 +130,7 @@
|
||||
type="danger"
|
||||
@click="handleDelete(row)"
|
||||
v-if="row.status === 1"
|
||||
v-hasPermi="'prison:release:delete'"
|
||||
v-hasPermi="['prison:release:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
@ -153,16 +153,18 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import ReleaseForm from './ReleaseForm.vue'
|
||||
import { ReleaseApi, type ReleasePageReqVO } from '@/api/prison/release'
|
||||
import { ReleaseForm } from './ReleaseForm.vue'
|
||||
import { ReleaseApi, type ReleasePageReqVO, type Release } from '@/api/prison/release'
|
||||
|
||||
defineOptions({ name: 'PrisonRelease' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage() // 统一使用 useMessage
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const tableData = ref<Release[]>([])
|
||||
const total = ref(0)
|
||||
const dateRange = ref([])
|
||||
const dateRange = ref<string[]>([])
|
||||
|
||||
const searchForm = reactive<ReleasePageReqVO>({
|
||||
prisonerNo: '',
|
||||
@ -208,7 +210,7 @@ const handleReset = () => {
|
||||
}
|
||||
|
||||
/** 日期范围变化 */
|
||||
const handleDateChange = (val: any) => {
|
||||
const handleDateChange = (val: string[] | null) => {
|
||||
if (val) {
|
||||
searchForm.actualReleaseDateStart = val[0]
|
||||
searchForm.actualReleaseDateEnd = val[1]
|
||||
@ -224,46 +226,43 @@ const handleCreate = () => {
|
||||
}
|
||||
|
||||
/** 编辑 */
|
||||
const handleEdit = (row: any) => {
|
||||
const handleEdit = (row: Release) => {
|
||||
formRef.value.open(row.id)
|
||||
}
|
||||
|
||||
/** 详情 */
|
||||
const handleDetail = (row: any) => {
|
||||
const handleDetail = (row: Release) => {
|
||||
formRef.value.open(row.id, true)
|
||||
}
|
||||
|
||||
/** 执行释放 */
|
||||
const handleDoRelease = (row: any) => {
|
||||
ElMessageBox.confirm(`确认要执行释放罪犯【${row.prisonerName}】吗?`, '操作确认', {
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const handleDoRelease = async (row: Release) => {
|
||||
try {
|
||||
await message.confirm(`确认要执行释放罪犯【${row.prisonerName}】吗?`)
|
||||
await ReleaseApi.doRelease(row.id)
|
||||
ElMessage.success('执行释放成功')
|
||||
message.success('执行释放成功')
|
||||
getPage()
|
||||
})
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 取消释放 */
|
||||
const handleCancel = (row: any) => {
|
||||
ElMessageBox.confirm(`确认要取消释放登记吗?`, '操作确认', {
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const handleCancel = async (row: Release) => {
|
||||
try {
|
||||
await message.confirm('确认要取消释放登记吗?')
|
||||
await ReleaseApi.cancelRelease(row.id)
|
||||
ElMessage.success('取消释放成功')
|
||||
message.success('取消释放成功')
|
||||
getPage()
|
||||
})
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
const handleDelete = (row: any) => {
|
||||
ElMessageBox.confirm(`确认要删除该释放登记吗?`, '操作确认', {
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const handleDelete = async (row: Release) => {
|
||||
try {
|
||||
await message.confirm('确认要删除该释放登记吗?')
|
||||
await ReleaseApi.deleteRelease(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
message.success('删除成功')
|
||||
getPage()
|
||||
})
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 状态样式 */
|
||||
|
||||
311
src/views/prison/report-template/ReportTemplateForm.vue
Normal file
311
src/views/prison/report-template/ReportTemplateForm.vue
Normal file
@ -0,0 +1,311 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="900px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="模板名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入模板名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="模板类型" prop="type">
|
||||
<el-select v-model="formData.type" placeholder="请选择模板类型" class="!w-100%">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_REPORT_TEMPLATE_TYPE)"
|
||||
: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="24">
|
||||
<el-form-item label="标题格式" prop="titleFormat">
|
||||
<el-input v-model="formData.titleFormat" placeholder="如:{罪犯姓名}服刑期间综合评估报告" />
|
||||
<div class="form-tip">可使用变量:{罪犯姓名}、{评估日期}、{监区名称}</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="设为默认" prop="isDefault">
|
||||
<el-switch v-model="formData.isDefault" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 评估维度配置 -->
|
||||
<el-form-item label="评估维度" prop="dimensions">
|
||||
<div class="dimension-container">
|
||||
<div class="dimension-header">
|
||||
<el-button type="primary" link @click="handleAddDimension">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 添加维度
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="formData.dimensions" border style="width: 100%">
|
||||
<el-table-column label="排序" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-input-number
|
||||
v-model="formData.dimensions[$index].sort"
|
||||
:min="0"
|
||||
:max="999"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="维度名称" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.name" placeholder="如:犯罪情况分析" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数据源" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.dataSources"
|
||||
multiple
|
||||
placeholder="选择数据源"
|
||||
size="small"
|
||||
class="!w-100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="ds in dataSourceOptions"
|
||||
:key="ds.value"
|
||||
:label="ds.label"
|
||||
:value="ds.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="AI生成" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.enableAi" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="输出格式" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.outputFormat" size="small" class="!w-100%">
|
||||
<el-option label="文本" value="text" />
|
||||
<el-option label="段落" value="paragraph" />
|
||||
<el-option label="列表" value="list" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="编辑器" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.editorType" size="small" class="!w-100%">
|
||||
<el-option label="文本框" value="text" />
|
||||
<el-option label="富文本" value="richtext" />
|
||||
<el-option label="下拉选择" value="select" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" link size="small" @click="handleRemoveDimension($index)">
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- AI提示词配置 -->
|
||||
<el-form-item label="AI提示词" prop="aiPromptConfig">
|
||||
<el-input
|
||||
v-model="formData.aiPromptConfig"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
placeholder="请输入AI提示词模板,用于指导AI生成评估内容"
|
||||
/>
|
||||
<div class="form-tip">
|
||||
提示:可以使用变量,如 {罪犯档案数据}、{考核数据}、{消费数据} 等
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { ReportTemplateApi, ReportTemplate, ReportDimension } from '@/api/prison/report'
|
||||
|
||||
/** 评估报告模板 表单 */
|
||||
defineOptions({ name: 'ReportTemplateForm' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
const formLoading = ref(false)
|
||||
const formType = ref('')
|
||||
const formRef = ref()
|
||||
|
||||
// 可选数据源
|
||||
const dataSourceOptions = [
|
||||
{ value: 'prisoner', label: '罪犯档案' },
|
||||
{ value: 'consumption', label: '消费记录' },
|
||||
{ value: 'score', label: '计分考核' },
|
||||
{ value: 'questionnaire_record', label: '问卷答题' },
|
||||
{ value: 'risk_assessment', label: '风险评估' },
|
||||
{ value: 'violation', label: '违规记录' },
|
||||
{ value: 'reward', label: '奖励记录' },
|
||||
{ value: 'visit', label: '会见记录' },
|
||||
{ value: 'labor', label: '劳动数据' },
|
||||
{ value: 'family', label: '家庭帮教' },
|
||||
{ value: 'psychology', label: '心理测评' }
|
||||
]
|
||||
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: '',
|
||||
type: undefined,
|
||||
titleFormat: '',
|
||||
dimensions: [] as ReportDimension[],
|
||||
aiPromptConfig: '',
|
||||
styleConfig: '',
|
||||
status: 1,
|
||||
isDefault: false,
|
||||
version: 1,
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '模板类型不能为空', trigger: 'change' }],
|
||||
titleFormat: [{ required: true, message: '标题格式不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await ReportTemplateApi.getTemplate(id)
|
||||
formData.value = {
|
||||
...data,
|
||||
dimensions: data.dimensions || []
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success'])
|
||||
const submitForm = async () => {
|
||||
await formRef.value.validate()
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value
|
||||
if (formType.value === 'create') {
|
||||
await ReportTemplateApi.createTemplate(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ReportTemplateApi.updateTemplate(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
type: undefined,
|
||||
titleFormat: '',
|
||||
dimensions: [
|
||||
{ name: '基本信息', dataSources: ['prisoner'], outputFormat: 'text', enableAi: false, editorType: 'text', sort: 1 },
|
||||
{ name: '服刑表现评估', dataSources: ['score'], outputFormat: 'paragraph', enableAi: true, editorType: 'richtext', sort: 2 },
|
||||
{ name: '消费行为分析', dataSources: ['consumption'], outputFormat: 'paragraph', enableAi: true, editorType: 'richtext', sort: 3 },
|
||||
{ name: '综合评估结论', dataSources: ['prisoner', 'score', 'risk_assessment'], outputFormat: 'paragraph', enableAi: true, editorType: 'richtext', sort: 4 }
|
||||
],
|
||||
aiPromptConfig: '',
|
||||
styleConfig: '',
|
||||
status: 1,
|
||||
isDefault: false,
|
||||
version: 1,
|
||||
remark: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 添加维度 */
|
||||
const handleAddDimension = () => {
|
||||
formData.value.dimensions.push({
|
||||
name: '',
|
||||
dataSources: [],
|
||||
outputFormat: 'paragraph',
|
||||
enableAi: true,
|
||||
editorType: 'richtext',
|
||||
sort: formData.value.dimensions.length + 1
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除维度 */
|
||||
const handleRemoveDimension = (index: number) => {
|
||||
formData.value.dimensions.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dimension-container {
|
||||
width: 100%;
|
||||
|
||||
.dimension-header {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 5px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
309
src/views/prison/report-template/index.vue
Normal file
309
src/views/prison/report-template/index.vue
Normal file
@ -0,0 +1,309 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="90px"
|
||||
>
|
||||
<el-form-item label="模板名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入模板名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="模板类型" prop="type">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-150px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_REPORT_TEMPLATE_TYPE)"
|
||||
: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-100px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_COMMON_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:report-template:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:report-template:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 模板列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="模板名称" prop="name" min-width="180" />
|
||||
<el-table-column label="模板类型" prop="type" width="150">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_REPORT_TEMPLATE_TYPE" :value="row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="评估维度" prop="dimensionCount" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag>{{ row.dimensions?.length || 0 }} 个</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="版本号" prop="version" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info" size="small">v{{ row.version }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="status" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="默认模板" prop="isDefault" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.isDefault" type="success" size="small">是</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="创建时间" prop="createTime" width="180" align="center" />
|
||||
<el-table-column label="操作" width="200" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', row.id)"
|
||||
v-hasPermi="['prison:report-template:update']"
|
||||
>
|
||||
<Icon icon="ep:edit" /> 修改
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
@click="handleCopy(row.id)"
|
||||
v-hasPermi="['prison:report-template:create']"
|
||||
>
|
||||
<Icon icon="ep:copy-document" /> 复制
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
:type="row.status === 1 ? 'warning' : 'success'"
|
||||
@click="handleToggleStatus(row)"
|
||||
v-hasPermi="['prison:report-template:update']"
|
||||
>
|
||||
<Icon :icon="row.status === 1 ? 'ep:video-pause' : 'ep:video-play'" />
|
||||
{{ row.status === 1 ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
v-if="!row.isDefault"
|
||||
type="primary"
|
||||
@click="handleSetDefault(row.id)"
|
||||
v-hasPermi="['prison:report-template:update']"
|
||||
>
|
||||
设为默认
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
v-hasPermi="['prison:report-template:delete']"
|
||||
>
|
||||
<Icon icon="ep: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>
|
||||
|
||||
<!-- 表单弹窗:添加/修改模板 -->
|
||||
<ReportTemplateForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import download from '@/utils/download'
|
||||
import { ReportTemplateApi, ReportTemplate } from '@/api/prison/report'
|
||||
import ReportTemplateForm from './ReportTemplateForm.vue'
|
||||
|
||||
defineOptions({ name: 'PrisonReportTemplate' })
|
||||
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const list = ref<ReportTemplate[]>([])
|
||||
const total = ref(0)
|
||||
const ids = ref<number[]>([])
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
status: undefined
|
||||
})
|
||||
const queryFormRef = ref()
|
||||
|
||||
/** 获取模板列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ReportTemplateApi.getTemplatePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: ReportTemplate[]) => {
|
||||
ids.value = selection.map((item) => item.id)
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 复制模板 */
|
||||
const handleCopy = async (id: number) => {
|
||||
try {
|
||||
await message.confirm(t('prison.reportTemplate.copyConfirm'))
|
||||
await ReportTemplateApi.copyTemplate(id)
|
||||
message.success(t('common.copySuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 切换状态 */
|
||||
const handleToggleStatus = async (row: ReportTemplate) => {
|
||||
try {
|
||||
await message.confirm(
|
||||
row.status === 1
|
||||
? t('prison.reportTemplate.disableConfirm')
|
||||
: t('prison.reportTemplate.enableConfirm')
|
||||
)
|
||||
await ReportTemplateApi.updateStatus(row.id, row.status === 1 ? 0 : 1)
|
||||
message.success(t('common.operationSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 设为默认 */
|
||||
const handleSetDefault = async (id: number) => {
|
||||
try {
|
||||
await message.confirm(t('prison.reportTemplate.setDefaultConfirm'))
|
||||
await ReportTemplateApi.setDefault(id)
|
||||
message.success(t('prison.reportTemplate.setDefaultSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await ReportTemplateApi.deleteTemplate(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除 */
|
||||
const handleBatchDelete = async () => {
|
||||
if (ids.value.length === 0) {
|
||||
return message.warning('请选择要删除的数据')
|
||||
}
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await ReportTemplateApi.deleteTemplateList(ids.value)
|
||||
message.success(t('common.delSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
await message.exportConfirm()
|
||||
exportLoading.value = true
|
||||
const data = await ReportTemplateApi.exportTemplate(queryParams)
|
||||
download.excel(data, '评估报告模板.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
230
src/views/prison/report/components/CreateReportDialog.vue
Normal file
230
src/views/prison/report/components/CreateReportDialog.vue
Normal file
@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<Dialog title="创建评估报告" v-model="dialogVisible" width="600px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="选择罪犯" prop="prisonerId">
|
||||
<el-select
|
||||
v-model="formData.prisonerId"
|
||||
placeholder="请选择罪犯"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="searchPrisoners"
|
||||
:loading="prisonerLoading"
|
||||
class="!w-100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in prisonerList"
|
||||
:key="item.id"
|
||||
:label="`${item.name} (${item.prisonerNo})`"
|
||||
:value="item.id"
|
||||
>
|
||||
<div class="prisoner-option">
|
||||
<span>{{ item.name }}</span>
|
||||
<span class="no">{{ item.prisonerNo }}</span>
|
||||
<el-tag size="small" :type="getRiskLevelType(item.riskLevel)">
|
||||
{{ getRiskLevelLabel(item.riskLevel) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="选择模板" prop="templateId">
|
||||
<el-select
|
||||
v-model="formData.templateId"
|
||||
placeholder="请选择报告模板"
|
||||
class="!w-100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in templateList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
>
|
||||
<span>{{ item.name }}</span>
|
||||
<el-tag size="small" type="info" class="ml-10px">
|
||||
{{ getTemplateTypeLabel(item.type) }}
|
||||
</el-tag>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="报告日期" prop="reportDate">
|
||||
<el-date-picker
|
||||
v-model="formData.reportDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择报告日期"
|
||||
class="!w-100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注(可选)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button @click="handleCreate" type="primary" :loading="formLoading" :disabled="!canCreate">
|
||||
确 定
|
||||
</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { ReportTemplateApi, ReportTemplate, ReportApi } from '@/api/prison/report'
|
||||
import { PrisonerSelectApi, PrisonerBrief } from '@/api/prison/report'
|
||||
|
||||
/** 创建报告弹窗 */
|
||||
defineOptions({ name: 'PrisonCreateReportDialog' })
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const formLoading = ref(false)
|
||||
const prisonerLoading = ref(false)
|
||||
const prisonerList = ref<PrisonerBrief[]>([])
|
||||
const templateList = ref<ReportTemplate[]>([])
|
||||
|
||||
const formData = ref({
|
||||
prisonerId: undefined as number | undefined,
|
||||
templateId: undefined as number | undefined,
|
||||
reportDate: '',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
prisonerId: [{ required: true, message: '请选择罪犯', trigger: 'change' }],
|
||||
templateId: [{ required: true, message: '请选择模板', trigger: 'change' }],
|
||||
reportDate: [{ required: true, message: '请选择报告日期', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const formRef = ref()
|
||||
|
||||
// 是否可以创建(已选择罪犯和模板)
|
||||
const canCreate = computed(() => {
|
||||
return formData.value.prisonerId && formData.value.templateId && formData.value.reportDate
|
||||
})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
|
||||
/** 加载模板列表 */
|
||||
const loadTemplates = async () => {
|
||||
const data = await ReportTemplateApi.getTemplatePage({ pageNo: 1, pageSize: 100, status: 1 })
|
||||
templateList.value = data.list
|
||||
}
|
||||
|
||||
/** 搜索罪犯 */
|
||||
const searchPrisoners = async (keyword: string) => {
|
||||
if (!keyword) {
|
||||
prisonerList.value = []
|
||||
return
|
||||
}
|
||||
prisonerLoading.value = true
|
||||
try {
|
||||
prisonerList.value = await PrisonerSelectApi.getAllPrisoners({
|
||||
name: keyword,
|
||||
prisonerNo: keyword
|
||||
})
|
||||
} finally {
|
||||
prisonerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取风险等级类型 */
|
||||
const getRiskLevelType = (level?: number): string => {
|
||||
const map: Record<number, string> = {
|
||||
1: 'success',
|
||||
2: 'warning',
|
||||
3: 'danger',
|
||||
4: 'danger'
|
||||
}
|
||||
return level ? map[level] || 'info' : 'info'
|
||||
}
|
||||
|
||||
/** 获取风险等级标签 */
|
||||
const getRiskLevelLabel = (level?: number): string => {
|
||||
const map: Record<number, string> = {
|
||||
1: '低风险',
|
||||
2: '中风险',
|
||||
3: '高风险',
|
||||
4: '极高风险'
|
||||
}
|
||||
return level ? map[level] || '-' : '未评估'
|
||||
}
|
||||
|
||||
/** 获取模板类型标签 */
|
||||
const getTemplateTypeLabel = (type: number): string => {
|
||||
const dict = getIntDictOptions(DICT_TYPE.PRISON_REPORT_TEMPLATE_TYPE).find((d: any) => d.value === type)
|
||||
return dict?.label || '-'
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
prisonerId: undefined,
|
||||
templateId: undefined,
|
||||
reportDate: '',
|
||||
remark: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 创建报告 */
|
||||
const handleCreate = async () => {
|
||||
await formRef.value.validate()
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await ReportApi.createReport({
|
||||
prisonerId: formData.value.prisonerId!,
|
||||
templateId: formData.value.templateId!,
|
||||
reportDate: formData.value.reportDate,
|
||||
remark: formData.value.remark
|
||||
} as any)
|
||||
dialogVisible.value = false
|
||||
message.success('创建成功')
|
||||
emit('success', data)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.prisoner-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.no {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.ml-10px {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
222
src/views/prison/report/components/QuickCommentDialog.vue
Normal file
222
src/views/prison/report/components/QuickCommentDialog.vue
Normal file
@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<Dialog title="快捷评语" v-model="dialogVisible" width="700px">
|
||||
<div class="quick-comment-dialog">
|
||||
<!-- 搜索区 -->
|
||||
<div class="search-area">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索评语关键词"
|
||||
prefix-icon="Search"
|
||||
clearable
|
||||
@input="handleSearch"
|
||||
class="search-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 分类标签 -->
|
||||
<div class="category-tabs">
|
||||
<el-tag
|
||||
:type="selectedCategoryId === null ? 'primary' : 'info'"
|
||||
@click="selectCategory(null)"
|
||||
class="category-tag"
|
||||
>
|
||||
全部
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-for="category in categoryList"
|
||||
:key="category.id"
|
||||
:type="selectedCategoryId === category.id ? 'primary' : 'info'"
|
||||
@click="selectCategory(category.id)"
|
||||
class="category-tag"
|
||||
>
|
||||
{{ category.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 评语列表 -->
|
||||
<div class="comment-list" v-loading="loading">
|
||||
<div
|
||||
v-for="comment in commentList"
|
||||
:key="comment.id"
|
||||
class="comment-item"
|
||||
@click="handleSelect(comment)"
|
||||
>
|
||||
<div class="comment-content">{{ comment.content }}</div>
|
||||
<div class="comment-meta">
|
||||
<span class="usage-count">使用 {{ comment.usageCount }} 次</span>
|
||||
<el-tag size="small" type="info" v-if="comment.categoryName">
|
||||
{{ comment.categoryName }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty v-if="!loading && commentList.length === 0" description="暂无评语" />
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-area" v-if="commentList.length > 0">
|
||||
<el-pagination
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
small
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="loadComments"
|
||||
@current-change="loadComments"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">关 闭</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { QuickCommentApi, QuickComment, CommentCategory } from '@/api/prison/report'
|
||||
|
||||
/** 快捷评语弹窗 */
|
||||
defineOptions({ name: 'PrisonQuickCommentDialog' })
|
||||
|
||||
const props = defineProps<{
|
||||
categoryType?: number // 评估类型
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const searchKeyword = ref('')
|
||||
const selectedCategoryId = ref<number | null>(null)
|
||||
const categoryList = ref<CommentCategory[]>([])
|
||||
const commentList = ref<QuickComment[]>([])
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
searchKeyword.value = ''
|
||||
selectedCategoryId.value = null
|
||||
page.value = 1
|
||||
loadCategories()
|
||||
loadComments()
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
|
||||
/** 加载分类列表 */
|
||||
const loadCategories = async () => {
|
||||
try {
|
||||
const data = await QuickCommentApi.getCategoryList({ type: props.categoryType })
|
||||
categoryList.value = data
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 加载评语列表 */
|
||||
const loadComments = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await QuickCommentApi.getCommentPage({
|
||||
pageNo: page.value,
|
||||
pageSize: pageSize.value,
|
||||
categoryId: selectedCategoryId.value || undefined,
|
||||
keyword: searchKeyword.value
|
||||
})
|
||||
commentList.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索 */
|
||||
const handleSearch = () => {
|
||||
page.value = 1
|
||||
loadComments()
|
||||
}
|
||||
|
||||
/** 选择分类 */
|
||||
const selectCategory = (categoryId: number | null) => {
|
||||
selectedCategoryId.value = categoryId
|
||||
page.value = 1
|
||||
loadComments()
|
||||
}
|
||||
|
||||
/** 选择评语 */
|
||||
const handleSelect = (comment: QuickComment) => {
|
||||
emit('insert', comment.content)
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
const emit = defineEmits(['insert'])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.quick-comment-dialog {
|
||||
.search-area {
|
||||
margin-bottom: 15px;
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.category-tabs {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.category-tag {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
|
||||
.comment-item {
|
||||
padding: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
background: #ecf5ff;
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.comment-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.usage-count {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-area {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
349
src/views/prison/report/components/ReportPreviewDialog.vue
Normal file
349
src/views/prison/report/components/ReportPreviewDialog.vue
Normal file
@ -0,0 +1,349 @@
|
||||
<template>
|
||||
<Dialog title="评估报告预览" v-model="dialogVisible" width="900px">
|
||||
<div v-loading="loading" class="report-preview">
|
||||
<!-- 报告信息头部 -->
|
||||
<div class="report-header" v-if="report">
|
||||
<h1 class="report-title">{{ report.title }}</h1>
|
||||
<div class="report-meta">
|
||||
<span class="meta-item">
|
||||
<span class="label">报告编号:</span>
|
||||
<span class="value">{{ report.reportNo }}</span>
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<span class="label">罪犯姓名:</span>
|
||||
<span class="value">{{ report.prisonerName }}</span>
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<span class="label">罪犯编号:</span>
|
||||
<span class="value">{{ report.prisonerNo }}</span>
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<span class="label">评估日期:</span>
|
||||
<span class="value">{{ report.reportDate }}</span>
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<span class="label">模板类型:</span>
|
||||
<span class="value">{{ report.templateName }}</span>
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<span class="label">风险等级:</span>
|
||||
<el-tag v-if="report.riskLevel" :type="getRiskLevelType(report.riskLevel)">
|
||||
{{ getRiskLevelLabel(report.riskLevel) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 状态标签 -->
|
||||
<div class="status-bar">
|
||||
<el-tag :type="getStatusType(report.status)" size="large">
|
||||
{{ getStatusLabel(report.status) }}
|
||||
</el-tag>
|
||||
<el-tag v-if="report.isAiGenerated" type="success" size="small" class="ai-tag">
|
||||
AI生成
|
||||
</el-tag>
|
||||
<el-tag v-if="report.signature" type="primary" size="small" class="signed-tag">
|
||||
已签名
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 报告内容 -->
|
||||
<div class="report-content" v-if="report">
|
||||
<!-- 维度内容 -->
|
||||
<div
|
||||
v-for="dimension in report.dimensions"
|
||||
:key="dimension.dimensionId"
|
||||
class="dimension-section"
|
||||
>
|
||||
<h3 class="dimension-title">
|
||||
{{ dimension.dimensionName }}
|
||||
<el-tag
|
||||
v-if="dimension.isAiGenerated"
|
||||
type="success"
|
||||
size="small"
|
||||
class="ai-badge"
|
||||
>
|
||||
AI生成
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-else-if="dimension.lastModifyTime"
|
||||
type="info"
|
||||
size="small"
|
||||
>
|
||||
已修改
|
||||
</el-tag>
|
||||
</h3>
|
||||
<div class="dimension-content">
|
||||
{{ dimension.content || '暂无内容' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 综合结论 -->
|
||||
<div v-if="report.conclusion" class="conclusion-section">
|
||||
<h3 class="section-title">综合结论</h3>
|
||||
<div class="section-content">{{ report.conclusion }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 改造建议 -->
|
||||
<div v-if="report.suggestions" class="suggestions-section">
|
||||
<h3 class="section-title">改造建议</h3>
|
||||
<div class="section-content">{{ report.suggestions }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 附件列表 -->
|
||||
<div v-if="report.attachments?.length" class="attachments-section">
|
||||
<h3 class="section-title">附件</h3>
|
||||
<el-upload
|
||||
:file-list="attachmentList"
|
||||
:disabled="true"
|
||||
list-type="picture-card"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 审核信息 -->
|
||||
<div v-if="report.reviewerName" class="review-info">
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="审核人">
|
||||
{{ report.reviewerName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审核时间">
|
||||
{{ report.reviewTime }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="report.reviewComment" label="审核意见" :span="2">
|
||||
{{ report.reviewComment }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">关 闭</el-button>
|
||||
<el-button
|
||||
v-if="report"
|
||||
type="primary"
|
||||
@click="handleExport('pdf')"
|
||||
:loading="exportLoading"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出PDF
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="report"
|
||||
type="success"
|
||||
@click="handleExport('word')"
|
||||
:loading="exportLoading"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出Word
|
||||
</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { ReportApi, type Report } from '@/api/prison/report'
|
||||
import type { UploadUserFile } from 'element-plus'
|
||||
|
||||
/** 报告预览弹窗 */
|
||||
defineOptions({ name: 'PrisonReportPreviewDialog' })
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const report = ref<Report | null>(null)
|
||||
const attachmentList = ref<UploadUserFile[]>([])
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
loadReport(id)
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
|
||||
/** 加载报告详情 */
|
||||
const loadReport = async (id: number) => {
|
||||
loading.value = true
|
||||
try {
|
||||
report.value = await ReportApi.getReport(id)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取风险等级类型 */
|
||||
const getRiskLevelType = (level: number): string => {
|
||||
const map: Record<number, string> = {
|
||||
1: 'success',
|
||||
2: 'warning',
|
||||
3: 'danger',
|
||||
4: 'danger'
|
||||
}
|
||||
return map[level] || 'info'
|
||||
}
|
||||
|
||||
/** 获取风险等级标签 */
|
||||
const getRiskLevelLabel = (level: number): string => {
|
||||
const map: Record<number, string> = {
|
||||
1: '低风险',
|
||||
2: '中风险',
|
||||
3: '高风险',
|
||||
4: '极高风险'
|
||||
}
|
||||
return map[level] || '-'
|
||||
}
|
||||
|
||||
/** 获取状态类型 */
|
||||
const getStatusType = (status: number): string => {
|
||||
const map: Record<number, string> = {
|
||||
1: 'info',
|
||||
2: 'warning',
|
||||
3: 'success',
|
||||
4: 'danger'
|
||||
}
|
||||
return map[status] || 'info'
|
||||
}
|
||||
|
||||
/** 获取状态标签 */
|
||||
const getStatusLabel = (status: number): string => {
|
||||
const dict = getIntDictOptions(DICT_TYPE.PRISON_REPORT_STATUS).find((d: any) => d.value === status)
|
||||
return dict?.label || '-'
|
||||
}
|
||||
|
||||
/** 导出报告 */
|
||||
const handleExport = async (format: 'pdf' | 'word') => {
|
||||
if (!report.value) return
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const data = await ReportApi.exportReport(report.value.id, format)
|
||||
const fileName = `${report.value.reportNo}_${report.value.prisonerName}.${format === 'pdf' ? 'pdf' : 'docx'}`
|
||||
// 使用下载功能
|
||||
message.success('导出成功')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.report-preview {
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.report-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
|
||||
.report-title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.report-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.meta-item {
|
||||
.label {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
|
||||
.ai-tag {
|
||||
background: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.signed-tag {
|
||||
background: #e3f2fd;
|
||||
color: #1565c0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.report-content {
|
||||
background: #fff;
|
||||
padding: 30px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dimension-section {
|
||||
margin-bottom: 25px;
|
||||
|
||||
.dimension-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.ai-badge {
|
||||
background: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
}
|
||||
|
||||
.dimension-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
color: #555;
|
||||
text-indent: 2em;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
color: #555;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.attachments-section {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.review-info {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
}
|
||||
</style>
|
||||
284
src/views/prison/report/components/VersionHistoryDialog.vue
Normal file
284
src/views/prison/report/components/VersionHistoryDialog.vue
Normal file
@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<Dialog title="历史版本" v-model="dialogVisible" width="900px">
|
||||
<div class="version-history-dialog">
|
||||
<!-- 版本列表 -->
|
||||
<div class="version-list" v-loading="loading">
|
||||
<el-table
|
||||
:data="versionList"
|
||||
stripe
|
||||
@row-click="handleSelectVersion"
|
||||
:row-class-name="getRowClassName"
|
||||
>
|
||||
<el-table-column label="版本号" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info" size="small">v{{ row.version }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="修改人" prop="modifierName" width="120" align="center" />
|
||||
<el-table-column label="修改时间" prop="modifyTime" width="180" align="center" />
|
||||
<el-table-column label="版本备注" prop="comment" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click.stop="handleCompare(row)"
|
||||
:disabled="!selectedVersion"
|
||||
>
|
||||
对比
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
link
|
||||
size="small"
|
||||
@click.stop="handleRestore(row)"
|
||||
:disabled="row.version === currentVersion"
|
||||
>
|
||||
恢复
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-empty v-if="!loading && versionList.length === 0" description="暂无历史版本" />
|
||||
</div>
|
||||
|
||||
<!-- 版本对比区 -->
|
||||
<div v-if="compareMode" class="version-compare">
|
||||
<div class="compare-header">
|
||||
<h4>版本对比</h4>
|
||||
<el-button type="primary" link @click="compareMode = false">关闭对比</el-button>
|
||||
</div>
|
||||
|
||||
<div class="compare-content">
|
||||
<div class="compare-panel left-panel">
|
||||
<div class="panel-header">
|
||||
<span>v{{ selectedVersion?.version }}</span>
|
||||
<span class="modifier">{{ selectedVersion?.modifierName }}</span>
|
||||
<span class="time">{{ selectedVersion?.modifyTime }}</span>
|
||||
</div>
|
||||
<div class="panel-content" v-html="diffHtml.oldContent"></div>
|
||||
</div>
|
||||
|
||||
<div class="compare-divider">
|
||||
<Icon icon="ep:arrow-right" />
|
||||
</div>
|
||||
|
||||
<div class="compare-panel right-panel">
|
||||
<div class="panel-header">
|
||||
<span>v{{ currentVersion }}</span>
|
||||
<span class="modifier">当前版本</span>
|
||||
</div>
|
||||
<div class="panel-content" v-html="diffHtml.newContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="compare-actions">
|
||||
<el-button @click="handleRestore(selectedVersion!)">
|
||||
<Icon icon="ep:refresh-left" class="mr-5px" /> 恢复到 v{{ selectedVersion?.version }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">关 闭</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ReportVersionApi, ReportVersion } from '@/api/prison/report'
|
||||
|
||||
/** 历史版本弹窗 */
|
||||
defineOptions({ name: 'PrisonVersionHistoryDialog' })
|
||||
|
||||
const props = defineProps<{
|
||||
reportId?: number
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const versionList = ref<ReportVersion[]>([])
|
||||
const currentVersion = ref(0)
|
||||
const selectedVersion = ref<ReportVersion | null>(null)
|
||||
const compareMode = ref(false)
|
||||
const diffHtml = ref({ oldContent: '', newContent: '' })
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
if (!props.reportId) {
|
||||
message.warning('请先保存报告')
|
||||
return
|
||||
}
|
||||
|
||||
dialogVisible.value = true
|
||||
compareMode.value = false
|
||||
selectedVersion.value = null
|
||||
loadVersions()
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
|
||||
/** 加载版本历史 */
|
||||
const loadVersions = async () => {
|
||||
if (!props.reportId) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ReportVersionApi.getVersionList(props.reportId)
|
||||
versionList.value = data
|
||||
if (data.length > 0) {
|
||||
currentVersion.value = data[0].version
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 选择版本查看 */
|
||||
const handleSelectVersion = async (row: ReportVersion) => {
|
||||
selectedVersion.value = row
|
||||
}
|
||||
|
||||
/** 获取行样式 */
|
||||
const getRowClassName = ({ row }: { row: ReportVersion }) => {
|
||||
return selectedVersion.value?.id === row.id ? 'selected-row' : ''
|
||||
}
|
||||
|
||||
/** 对比版本 */
|
||||
const handleCompare = async (version: ReportVersion) => {
|
||||
selectedVersion.value = version
|
||||
compareMode.value = true
|
||||
|
||||
// 获取版本对比数据
|
||||
try {
|
||||
// const data = await ReportVersionApi.compareVersions(version.id, currentVersionId)
|
||||
// diffHtml.value = data
|
||||
|
||||
// 模拟对比数据
|
||||
diffHtml.value = {
|
||||
oldContent: `<div style="padding: 10px; line-height: 1.6;">
|
||||
<p><span style="background: #ffcccc;">该犯在服刑期间表现良好,</span>遵守监规纪律,</p>
|
||||
<p><span style="background: #ffcccc;">积极参加劳动,</span>完成劳动任务。</p>
|
||||
<p><span style="background: #ffcccc;">月度考核成绩均在85分以上,</span>无违规记录。</p>
|
||||
</div>`,
|
||||
newContent: `<div style="padding: 10px; line-height: 1.6;">
|
||||
<p><span style="background: #ccffcc;">该犯在服刑期间表现优秀,严格遵守监规纪律,</span>遵守监规纪律,</p>
|
||||
<p><span style="background: #ccffcc;">积极参加劳动生产,</span>完成劳动任务。</p>
|
||||
<p><span style="background: #ccffcc;">月度考核成绩均在90分以上,</span>无违规记录。</p>
|
||||
</div>`
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 恢复版本 */
|
||||
const handleRestore = async (version: ReportVersion) => {
|
||||
try {
|
||||
await message.confirm(`确认要恢复到 v${version.version} 版本吗?当前版本内容将被覆盖。`)
|
||||
await ReportVersionApi.restoreVersion(version.id)
|
||||
message.success('版本已恢复')
|
||||
emit('restore', version.id)
|
||||
loadVersions()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const emit = defineEmits(['restore'])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.version-history-dialog {
|
||||
.version-list {
|
||||
margin-bottom: 20px;
|
||||
|
||||
:deep(.selected-row) {
|
||||
background: #ecf5ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__row) {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.version-compare {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
.compare-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
background: #f5f7fa;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.compare-content {
|
||||
display: flex;
|
||||
min-height: 300px;
|
||||
|
||||
.compare-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.panel-header {
|
||||
padding: 10px 15px;
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
|
||||
.modifier {
|
||||
margin-left: 10px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.time {
|
||||
margin-left: 10px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
overflow-y: auto;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
&.left-panel {
|
||||
border-right: 1px solid #e4e7ed;
|
||||
}
|
||||
}
|
||||
|
||||
.compare-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
background: #f5f7fa;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.compare-actions {
|
||||
padding: 15px;
|
||||
background: #f5f7fa;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
899
src/views/prison/report/edit/index.vue
Normal file
899
src/views/prison/report/edit/index.vue
Normal file
@ -0,0 +1,899 @@
|
||||
<template>
|
||||
<div class="report-edit-container">
|
||||
<!-- 左侧罪犯列表 -->
|
||||
<div class="prisoner-sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h3>服刑人员列表</h3>
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索姓名/编号"
|
||||
prefix-icon="Search"
|
||||
clearable
|
||||
@input="handleSearch"
|
||||
@clear="loadPrisonerList"
|
||||
class="search-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="prisoner-list" v-loading="prisonerLoading">
|
||||
<div
|
||||
v-for="prisoner in prisonerList"
|
||||
:key="prisoner.id"
|
||||
:class="['prisoner-card', { active: currentPrisoner?.id === prisoner.id }]"
|
||||
@click="selectPrisoner(prisoner)"
|
||||
>
|
||||
<div class="card-header">
|
||||
<span class="area-name">{{ prisoner.areaName }}</span>
|
||||
<el-tag
|
||||
v-if="prisoner.riskLevel"
|
||||
:type="getRiskLevelType(prisoner.riskLevel)"
|
||||
size="small"
|
||||
>
|
||||
{{ getRiskLevelLabel(prisoner.riskLevel) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="prisoner-name">{{ prisoner.name }}</span>
|
||||
<span class="prisoner-no">{{ prisoner.prisonerNo }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty v-if="!prisonerLoading && prisonerList.length === 0" description="暂无数据" />
|
||||
</div>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<el-pagination
|
||||
v-model:current-page="prisonerPage"
|
||||
v-model:page-size="prisonerPageSize"
|
||||
:total="prisonerTotal"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
small
|
||||
layout="prev, pager, next, total"
|
||||
@current-change="loadPrisonerList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧报告编辑区 -->
|
||||
<div class="report-editor">
|
||||
<!-- 顶部操作栏 -->
|
||||
<div class="editor-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<el-button @click="goBack">
|
||||
<Icon icon="ep:arrow-left" class="mr-5px" /> 返回
|
||||
</el-button>
|
||||
<span class="toolbar-divider">|</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleAiGenerateAll"
|
||||
:loading="aiGenerating"
|
||||
:disabled="!currentReport"
|
||||
>
|
||||
<Icon icon="ep:magic-stick" class="mr-5px" /> AI生成全部
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="handleSaveDraft"
|
||||
:loading="saving"
|
||||
:disabled="!currentReport"
|
||||
>
|
||||
<Icon icon="ep:document-checked" class="mr-5px" /> 保存草稿
|
||||
<span v-if="lastSaveTime" class="save-time"> ({{ lastSaveTime }})</span>
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-center">
|
||||
<span class="report-title">{{ currentReport?.title || '未选择报告' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-right">
|
||||
<el-button
|
||||
@click="handleShowQuickComment"
|
||||
:disabled="!currentReport"
|
||||
>
|
||||
<Icon icon="ep:tickets" class="mr-5px" /> 快捷评语
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="handleShowVersionHistory"
|
||||
:disabled="!currentReport"
|
||||
>
|
||||
<Icon icon="ep:clock" class="mr-5px" /> 历史版本
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
@click="handleSubmitReview"
|
||||
:disabled="!canSubmit"
|
||||
v-hasPermi="['prison:report:submit']"
|
||||
>
|
||||
<Icon icon="ep:position" class="mr-5px" /> 提交审核
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="handlePreview"
|
||||
:disabled="!currentReport"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> 预览
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleExport('pdf')"
|
||||
:disabled="!currentReport"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 基本信息区 -->
|
||||
<div class="basic-info" v-if="currentReport">
|
||||
<el-descriptions :column="4" border size="small">
|
||||
<el-descriptions-item label="服刑人员">
|
||||
{{ currentReport.prisonerName }} ({{ currentReport.prisonerNo }})
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="监区">
|
||||
{{ currentReport.areaName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="评估日期">
|
||||
{{ currentReport.reportDate }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="模板">
|
||||
{{ currentReport.templateName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="风险等级">
|
||||
<el-tag
|
||||
v-if="currentReport.riskLevel"
|
||||
:type="getRiskLevelType(currentReport.riskLevel)"
|
||||
>
|
||||
{{ getRiskLevelLabel(currentReport.riskLevel) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_REPORT_STATUS" :value="currentReport.status" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="版本">
|
||||
v{{ currentReport.version }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="数据源状态">
|
||||
<el-tag v-if="dataSourceStatus.complete" type="success" size="small">
|
||||
已加载 {{ dataSourceStatus.loaded }}/{{ dataSourceStatus.total }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="warning" size="small" loading>
|
||||
加载中...
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 审核退回提示 -->
|
||||
<div v-if="currentReport?.status === 4 && currentReport.reviewComment" class="reject-notice">
|
||||
<el-alert
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<template #title>
|
||||
审核退回原因:{{ currentReport.reviewComment }}
|
||||
<el-button type="primary" link size="small" @click="scrollToContent">
|
||||
立即修改
|
||||
</el-button>
|
||||
</template>
|
||||
</el-alert>
|
||||
</div>
|
||||
|
||||
<!-- 编辑内容区 -->
|
||||
<div class="editor-content" v-if="currentReport" ref="contentRef">
|
||||
<!-- 维度分析区 -->
|
||||
<div class="dimension-editor">
|
||||
<el-collapse v-model="activeDimensions">
|
||||
<el-collapse-item
|
||||
v-for="(dimension, index) in currentReport.dimensions"
|
||||
:key="dimension.dimensionId"
|
||||
:name="dimension.dimensionId"
|
||||
:title="dimension.dimensionName"
|
||||
>
|
||||
<template #title>
|
||||
<div class="dimension-title-bar">
|
||||
<span class="dimension-name">{{ index + 1 }}. {{ dimension.dimensionName }}</span>
|
||||
<div class="dimension-tags">
|
||||
<el-tag
|
||||
v-if="dimension.isAiGenerated"
|
||||
type="success"
|
||||
size="small"
|
||||
>
|
||||
AI生成
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-else-if="dimension.lastModifyTime"
|
||||
type="info"
|
||||
size="small"
|
||||
>
|
||||
已修改
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-if="dimension.aiGenerateTime"
|
||||
type="info"
|
||||
size="small"
|
||||
>
|
||||
{{ dimension.aiGenerateTime }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="dimension-content">
|
||||
<el-input
|
||||
v-model="dimension.content"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
placeholder="请输入评估内容"
|
||||
@change="markDimensionModified(dimension)"
|
||||
/>
|
||||
|
||||
<div class="dimension-actions">
|
||||
<el-button
|
||||
v-if="dimension.enableAi"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleAiGenerate(dimension)"
|
||||
:loading="aiGenerating && generatingDimensionId === dimension.dimensionId"
|
||||
>
|
||||
<Icon icon="ep:magic-stick" class="mr-5px" />
|
||||
{{ dimension.isAiGenerated ? '重新生成' : 'AI生成' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="dimension.lastModifyTime && dimension.isAiGenerated"
|
||||
size="small"
|
||||
@click="handleRestoreAiContent(dimension)"
|
||||
>
|
||||
恢复AI内容
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
|
||||
<!-- 综合结论与建议 -->
|
||||
<div class="conclusion-section">
|
||||
<h3 class="section-title">综合结论与建议</h3>
|
||||
|
||||
<el-form label-width="100px" size="default">
|
||||
<el-form-item label="风险等级">
|
||||
<el-select v-model="currentReport.riskLevel" placeholder="请选择风险等级">
|
||||
<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="综合结论">
|
||||
<el-input
|
||||
v-model="currentReport.conclusion"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入综合结论"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="改造建议">
|
||||
<el-input
|
||||
v-model="currentReport.suggestions"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入改造建议"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="empty-state">
|
||||
<el-empty description="请从左侧选择服刑人员开始编辑报告">
|
||||
<el-button type="primary" @click="openCreateDialog">创建报告</el-button>
|
||||
</el-empty>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快捷评语弹窗 -->
|
||||
<QuickCommentDialog
|
||||
ref="quickCommentDialogRef"
|
||||
:category-type="1"
|
||||
@insert="insertComment"
|
||||
/>
|
||||
|
||||
<!-- 历史版本弹窗 -->
|
||||
<VersionHistoryDialog
|
||||
ref="versionDialogRef"
|
||||
:report-id="currentReport?.id"
|
||||
@restore="handleRestoreVersion"
|
||||
/>
|
||||
|
||||
<!-- 报告预览弹窗 -->
|
||||
<ReportPreviewDialog ref="previewDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ReportApi, Report, ReportDimensionContent } from '@/api/prison/report'
|
||||
import { PrisonerSelectApi, PrisonerBrief } from '@/api/prison/report'
|
||||
import QuickCommentDialog from '@/views/prison/report/components/QuickCommentDialog.vue'
|
||||
import VersionHistoryDialog from '@/views/prison/report/components/VersionHistoryDialog.vue'
|
||||
import ReportPreviewDialog from '@/views/prison/report/components/ReportPreviewDialog.vue'
|
||||
|
||||
/** 评估报告编辑页面 */
|
||||
defineOptions({ name: 'PrisonReportEdit' })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
|
||||
// 左侧罪犯列表
|
||||
const prisonerLoading = ref(false)
|
||||
const prisonerList = ref<PrisonerBrief[]>([])
|
||||
const prisonerPage = ref(1)
|
||||
const prisonerPageSize = ref(20)
|
||||
const prisonerTotal = ref(0)
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 右侧编辑区
|
||||
const currentPrisoner = ref<PrisonerBrief | null>(null)
|
||||
const currentReport = ref<Report | null>(null)
|
||||
const activeDimensions = ref<number[]>([])
|
||||
const lastSaveTime = ref<string | null>(null)
|
||||
|
||||
// 状态
|
||||
const saving = ref(false)
|
||||
const aiGenerating = ref(false)
|
||||
const generatingDimensionId = ref<number | null>(null)
|
||||
const dataSourceStatus = ref({ total: 6, loaded: 0, complete: false })
|
||||
|
||||
// 组件引用
|
||||
const contentRef = ref()
|
||||
const quickCommentDialogRef = ref()
|
||||
const versionDialogRef = ref()
|
||||
const previewDialogRef = ref()
|
||||
|
||||
/** 加载罪犯列表 */
|
||||
const loadPrisonerList = async () => {
|
||||
prisonerLoading.value = true
|
||||
try {
|
||||
const data = await PrisonerSelectApi.getPrisonerPage({
|
||||
pageNo: prisonerPage.value,
|
||||
pageSize: prisonerPageSize.value,
|
||||
name: searchKeyword.value,
|
||||
prisonerNo: searchKeyword.value
|
||||
})
|
||||
prisonerList.value = data.list
|
||||
prisonerTotal.value = data.total
|
||||
} finally {
|
||||
prisonerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索罪犯 */
|
||||
const handleSearch = () => {
|
||||
prisonerPage.value = 1
|
||||
if (searchKeyword.value) {
|
||||
// 防抖搜索
|
||||
setTimeout(() => loadPrisonerList(), 300)
|
||||
} else {
|
||||
loadPrisonerList()
|
||||
}
|
||||
}
|
||||
|
||||
/** 选择罪犯 */
|
||||
const selectPrisoner = async (prisoner: PrisonerBrief) => {
|
||||
currentPrisoner.value = prisoner
|
||||
// 检查是否已有报告
|
||||
await loadReportForPrisoner(prisoner.id)
|
||||
}
|
||||
|
||||
/** 为罪犯加载/创建报告 */
|
||||
const loadReportForPrisoner = async (prisonerId: number) => {
|
||||
try {
|
||||
// 这里应该调用API获取该罪犯的最新报告
|
||||
// 暂时使用模拟数据
|
||||
// const data = await ReportApi.getLatestReportByPrisoner(prisonerId)
|
||||
// currentReport.value = data
|
||||
|
||||
// 模拟初始化报告数据
|
||||
currentReport.value = {
|
||||
id: Date.now(),
|
||||
reportNo: `BG${new Date().toISOString().slice(0, 10).replace(/-/g, '')}${String(Math.floor(Math.random() * 10000)).padStart(4, '0')}`,
|
||||
prisonerId: prisonerId,
|
||||
prisonerNo: currentPrisoner.value?.prisonerNo || '',
|
||||
prisonerName: currentPrisoner.value?.name || '',
|
||||
templateId: 1,
|
||||
templateName: '入监综合评估报告',
|
||||
title: `${currentPrisoner.value?.name}服刑期间综合评估报告`,
|
||||
reportDate: new Date().toISOString().slice(0, 10),
|
||||
dimensions: [
|
||||
{
|
||||
dimensionId: 1,
|
||||
dimensionName: '基本信息',
|
||||
content: '',
|
||||
isAiGenerated: false,
|
||||
enableAi: false,
|
||||
sort: 1
|
||||
},
|
||||
{
|
||||
dimensionId: 2,
|
||||
dimensionName: '犯罪情况分析',
|
||||
content: '',
|
||||
isAiGenerated: true,
|
||||
enableAi: true,
|
||||
sort: 2
|
||||
},
|
||||
{
|
||||
dimensionId: 3,
|
||||
dimensionName: '服刑表现评估',
|
||||
content: '',
|
||||
isAiGenerated: true,
|
||||
enableAi: true,
|
||||
sort: 3
|
||||
},
|
||||
{
|
||||
dimensionId: 4,
|
||||
dimensionName: '消费行为分析',
|
||||
content: '',
|
||||
isAiGenerated: true,
|
||||
enableAi: true,
|
||||
sort: 4
|
||||
},
|
||||
{
|
||||
dimensionId: 5,
|
||||
dimensionName: '综合评估结论',
|
||||
content: '',
|
||||
isAiGenerated: true,
|
||||
enableAi: true,
|
||||
sort: 5
|
||||
}
|
||||
],
|
||||
status: 1,
|
||||
version: 1
|
||||
}
|
||||
|
||||
// 默认展开所有维度
|
||||
activeDimensions.value = currentReport.value.dimensions.map(d => d.dimensionId)
|
||||
updateDataSourceStatus()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 更新数据源状态 */
|
||||
const updateDataSourceStatus = () => {
|
||||
// 模拟数据源加载完成
|
||||
dataSourceStatus.value = { total: 6, loaded: 6, complete: true }
|
||||
}
|
||||
|
||||
/** 获取风险等级类型 */
|
||||
const getRiskLevelType = (level?: number): string => {
|
||||
const map: Record<number, string> = {
|
||||
1: 'success',
|
||||
2: 'warning',
|
||||
3: 'danger',
|
||||
4: 'danger'
|
||||
}
|
||||
return level ? map[level] || 'info' : 'info'
|
||||
}
|
||||
|
||||
/** 获取风险等级标签 */
|
||||
const getRiskLevelLabel = (level?: number): string => {
|
||||
const map: Record<number, string> = {
|
||||
1: '低风险',
|
||||
2: '中风险',
|
||||
3: '高风险',
|
||||
4: '极高风险'
|
||||
}
|
||||
return level ? map[level] || '-' : '未评估'
|
||||
}
|
||||
|
||||
/** 标记维度已修改 */
|
||||
const markDimensionModified = (dimension: ReportDimensionContent) => {
|
||||
dimension.isAiGenerated = false
|
||||
dimension.lastModifyTime = new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||||
}
|
||||
|
||||
/** AI生成单个维度 */
|
||||
const handleAiGenerate = async (dimension: ReportDimensionContent) => {
|
||||
if (!currentReport.value) return
|
||||
|
||||
generatingDimensionId.value = dimension.dimensionId
|
||||
aiGenerating.value = true
|
||||
|
||||
try {
|
||||
// 调用AI生成接口
|
||||
await ReportApi.generateReportByAi(currentReport.value.id, [dimension.dimensionId])
|
||||
|
||||
// 重新获取报告数据
|
||||
const data = await ReportApi.getReport(currentReport.value.id)
|
||||
currentReport.value = data
|
||||
message.success(t('prison.report.aiGenerateComplete'))
|
||||
} catch {
|
||||
message.error(t('prison.report.aiGenerateFailed'))
|
||||
} finally {
|
||||
aiGenerating.value = false
|
||||
generatingDimensionId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
/** AI生成全部 */
|
||||
const handleAiGenerateAll = async () => {
|
||||
if (!currentReport.value) return
|
||||
|
||||
aiGenerating.value = true
|
||||
|
||||
try {
|
||||
// 调用批量AI生成接口
|
||||
await ReportApi.generateReportByAi(currentReport.value.id)
|
||||
|
||||
// 重新获取报告数据
|
||||
const data = await ReportApi.getReport(currentReport.value.id)
|
||||
currentReport.value = data
|
||||
message.success(t('prison.report.aiGenerateAllComplete'))
|
||||
} catch {
|
||||
message.error(t('prison.report.aiGenerateFailed'))
|
||||
} finally {
|
||||
aiGenerating.value = false
|
||||
generatingDimensionId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存草稿 */
|
||||
const handleSaveDraft = async () => {
|
||||
if (!currentReport.value) return
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
// 调用保存接口
|
||||
await ReportApi.updateReport(currentReport.value)
|
||||
|
||||
lastSaveTime.value = new Date().toLocaleTimeString()
|
||||
message.success(t('common.saveSuccess'))
|
||||
} catch {
|
||||
message.error(t('common.saveFailed'))
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交审核 */
|
||||
const handleSubmitReview = async () => {
|
||||
if (!currentReport.value) return
|
||||
|
||||
try {
|
||||
await message.confirm(t('prison.report.submitConfirm'))
|
||||
await ReportApi.submitReport(currentReport.value.id)
|
||||
message.success(t('prison.report.submitSuccess'))
|
||||
// 刷新报告状态
|
||||
currentReport.value.status = 2
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 恢复AI原始内容 */
|
||||
const handleRestoreAiContent = async (dimension: ReportDimensionContent) => {
|
||||
try {
|
||||
await message.confirm(t('prison.report.restoreAiConfirm'))
|
||||
// 调用接口获取AI原始内容
|
||||
await ReportApi.generateReportByAi(currentReport.value!.id, [dimension.dimensionId])
|
||||
// 重新获取报告数据
|
||||
const data = await ReportApi.getReport(currentReport.value!.id)
|
||||
currentReport.value = data
|
||||
message.success(t('prison.report.restoreAiSuccess'))
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 预览报告 */
|
||||
const handlePreview = () => {
|
||||
if (currentReport.value) {
|
||||
previewDialogRef.value?.open(currentReport.value.id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 导出报告 */
|
||||
const handleExport = async (format: 'pdf' | 'word') => {
|
||||
if (!currentReport.value) return
|
||||
|
||||
try {
|
||||
const data = await ReportApi.exportReport(currentReport.value.id, format)
|
||||
const fileName = `${currentReport.value.reportNo}_${currentReport.value.prisonerName}.${format}`
|
||||
message.success('导出成功')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 显示快捷评语 */
|
||||
const handleShowQuickComment = () => {
|
||||
quickCommentDialogRef.value?.open()
|
||||
}
|
||||
|
||||
/** 插入快捷评语 */
|
||||
const insertComment = (comment: string) => {
|
||||
message.success(t('prison.report.commentInserted'))
|
||||
}
|
||||
|
||||
/** 显示历史版本 */
|
||||
const handleShowVersionHistory = () => {
|
||||
versionDialogRef.value?.open()
|
||||
}
|
||||
|
||||
/** 恢复版本 */
|
||||
const handleRestoreVersion = async (versionId: number) => {
|
||||
try {
|
||||
await ReportVersionApi.restoreVersion(versionId)
|
||||
message.success(t('prison.report.versionRestored'))
|
||||
// 刷新报告内容
|
||||
loadReportForPrisoner(currentPrisoner.value!.id)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 跳转到内容区 */
|
||||
const scrollToContent = () => {
|
||||
contentRef.value?.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
|
||||
/** 返回列表 */
|
||||
const goBack = () => {
|
||||
router.push('/prison/report')
|
||||
}
|
||||
|
||||
/** 打开创建报告弹窗 */
|
||||
const openCreateDialog = () => {
|
||||
// 导航到创建页面
|
||||
router.push('/prison/report?action=create')
|
||||
}
|
||||
|
||||
/** 是否可以提交 */
|
||||
const canSubmit = computed(() => {
|
||||
if (!currentReport.value) return false
|
||||
if (currentReport.value.status !== 1 && currentReport.value.status !== 4) return false
|
||||
// 检查是否所有必填项都已填写
|
||||
return currentReport.value.dimensions.every(d => d.content)
|
||||
})
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
loadPrisonerList()
|
||||
|
||||
// 如果有报告ID参数,加载报告
|
||||
const reportId = route.query.id
|
||||
if (reportId) {
|
||||
// loadReport(Number(reportId))
|
||||
}
|
||||
})
|
||||
|
||||
// 键盘快捷键
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', handleKeydown)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', handleKeydown)
|
||||
})
|
||||
|
||||
/** 处理键盘快捷键 */
|
||||
const handleKeydown = (e: KeyboardEvent) => {
|
||||
// Ctrl + S 保存
|
||||
if (e.ctrlKey && e.key === 's') {
|
||||
e.preventDefault()
|
||||
handleSaveDraft()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.report-edit-container {
|
||||
display: flex;
|
||||
height: calc(100vh - 84px);
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
/* 左侧罪犯列表 */
|
||||
.prisoner-sidebar {
|
||||
width: 280px;
|
||||
background: #fff;
|
||||
border-right: 1px solid #e4e7ed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.sidebar-header {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.prisoner-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.prisoner-card {
|
||||
padding: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
background: #ecf5ff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #409eff;
|
||||
background: #ecf5ff;
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.area-name {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.prisoner-name {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.prisoner-no {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
padding: 10px;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 右侧报告编辑区 */
|
||||
.report-editor {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.editor-toolbar {
|
||||
height: 50px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
|
||||
.toolbar-left,
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.toolbar-divider {
|
||||
margin: 0 10px;
|
||||
color: #e4e7ed;
|
||||
}
|
||||
|
||||
.toolbar-center {
|
||||
.report-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.save-time {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.basic-info {
|
||||
padding: 15px 20px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.reject-notice {
|
||||
padding: 10px 20px;
|
||||
background: #fdf6ec;
|
||||
}
|
||||
|
||||
.editor-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
|
||||
.dimension-editor {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.dimension-title-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.dimension-name {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.dimension-tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.dimension-content {
|
||||
.dimension-actions {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.conclusion-section {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0 0 20px 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画 */
|
||||
.prisoner-card {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
</style>
|
||||
421
src/views/prison/report/index.vue
Normal file
421
src/views/prison/report/index.vue
Normal file
@ -0,0 +1,421 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="90px"
|
||||
>
|
||||
<el-form-item label="报告编号" prop="reportNo">
|
||||
<el-input
|
||||
v-model="queryParams.reportNo"
|
||||
placeholder="请输入报告编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-180px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯编号" prop="prisonerNo">
|
||||
<el-input
|
||||
v-model="queryParams.prisonerNo"
|
||||
placeholder="请输入罪犯编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-140px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="模板类型" prop="templateId">
|
||||
<el-select
|
||||
v-model="queryParams.templateId"
|
||||
placeholder="请选择模板"
|
||||
clearable
|
||||
class="!w-160px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in templateOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="报告状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-110px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_REPORT_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="报告日期" prop="reportDate">
|
||||
<el-date-picker
|
||||
v-model="queryParams.reportDate"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
class="!w-220px"
|
||||
/>
|
||||
</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="openCreateDialog"
|
||||
v-hasPermi="['prison:report:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 创建报告
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:report:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 报告列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="报告编号" prop="reportNo" width="180" align="center" />
|
||||
<el-table-column label="罪犯信息" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div class="prisoner-info">
|
||||
<el-avatar :size="32" :src="row.avatarUrl">{{ row.prisonerName?.charAt(0) }}</el-avatar>
|
||||
<div class="prisoner-detail">
|
||||
<span class="name">{{ row.prisonerName }}</span>
|
||||
<span class="no">{{ row.prisonerNo }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报告标题" prop="title" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column label="模板类型" prop="templateName" width="150" />
|
||||
<el-table-column label="报告日期" prop="reportDate" width="120" align="center" />
|
||||
<el-table-column label="风险等级" prop="riskLevel" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.riskLevel" :type="getRiskLevelType(row.riskLevel)" size="small">
|
||||
{{ getRiskLevelLabel(row.riskLevel) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="status" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_REPORT_STATUS" :value="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="版本" prop="version" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info" size="small">v{{ row.version }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提交人" prop="submitterName" width="100" align="center" />
|
||||
<el-table-column label="审核人" prop="reviewerName" width="100" align="center" />
|
||||
<el-table-column label="创建时间" prop="createTime" width="180" align="center" />
|
||||
<el-table-column label="操作" width="280" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleEdit(row)"
|
||||
v-hasPermi="['prison:report:update']"
|
||||
>
|
||||
<Icon icon="ep:edit" /> 编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handlePreview(row)"
|
||||
>
|
||||
<Icon icon="ep:view" /> 预览
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
@click="handleDownload(row, 'pdf')"
|
||||
v-hasPermi="['prison:report:export']"
|
||||
>
|
||||
<Icon icon="ep:download" /> 下载
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 1"
|
||||
link
|
||||
type="warning"
|
||||
@click="handleSubmit(row.id)"
|
||||
v-hasPermi="['prison:report:submit']"
|
||||
>
|
||||
提交审核
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 4"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleResubmit(row.id)"
|
||||
v-hasPermi="['prison:report:update']"
|
||||
>
|
||||
重新提交
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
v-hasPermi="['prison:report:delete']"
|
||||
>
|
||||
<Icon icon="ep: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>
|
||||
|
||||
<!-- 创建报告弹窗 -->
|
||||
<CreateReportDialog ref="createDialogRef" @success="getList" />
|
||||
|
||||
<!-- 报告预览弹窗 -->
|
||||
<ReportPreviewDialog ref="previewDialogRef" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import download from '@/utils/download'
|
||||
import { ReportApi, ReportPageParams, Report, ReportTemplateApi } from '@/api/prison/report'
|
||||
import CreateReportDialog from './components/CreateReportDialog.vue'
|
||||
import ReportPreviewDialog from './components/ReportPreviewDialog.vue'
|
||||
|
||||
defineOptions({ name: 'PrisonReport' })
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const list = ref<Report[]>([])
|
||||
const total = ref(0)
|
||||
const ids = ref<number[]>([])
|
||||
const templateOptions = ref<{ id: number; name: string }[]>([])
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
reportNo: undefined,
|
||||
prisonerNo: undefined,
|
||||
templateId: undefined,
|
||||
status: undefined,
|
||||
reportDate: undefined as string[] | undefined
|
||||
})
|
||||
const queryFormRef = ref()
|
||||
|
||||
/** 获取风险等级类型 */
|
||||
const getRiskLevelType = (level: number): string => {
|
||||
const map: Record<number, string> = {
|
||||
1: 'success',
|
||||
2: 'warning',
|
||||
3: 'danger',
|
||||
4: 'danger'
|
||||
}
|
||||
return map[level] || 'info'
|
||||
}
|
||||
|
||||
/** 获取风险等级标签 */
|
||||
const getRiskLevelLabel = (level: number): string => {
|
||||
const map: Record<number, string> = {
|
||||
1: '低风险',
|
||||
2: '中风险',
|
||||
3: '高风险',
|
||||
4: '极高风险'
|
||||
}
|
||||
return map[level] || '-'
|
||||
}
|
||||
|
||||
/** 获取报告列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ReportApi.getReportPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取模板列表 */
|
||||
const getTemplateList = async () => {
|
||||
const data = await ReportTemplateApi.getTemplatePage({ pageNo: 1, pageSize: 100, status: 1 })
|
||||
templateOptions.value = data.list.map((item: any) => ({ id: item.id, name: item.name }))
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: Report[]) => {
|
||||
ids.value = selection.map((item) => item.id)
|
||||
}
|
||||
|
||||
/** 打开创建报告弹窗 */
|
||||
const createDialogRef = ref()
|
||||
const openCreateDialog = () => {
|
||||
createDialogRef.value?.open()
|
||||
}
|
||||
|
||||
/** 预览报告弹窗 */
|
||||
const previewDialogRef = ref()
|
||||
|
||||
/** 编辑报告 */
|
||||
const handleEdit = (row: Report) => {
|
||||
router.push({
|
||||
path: '/prison/report/edit',
|
||||
query: { id: row.id }
|
||||
})
|
||||
}
|
||||
|
||||
/** 预览报告 */
|
||||
const handlePreview = (row: Report) => {
|
||||
previewDialogRef.value?.open(row.id)
|
||||
}
|
||||
|
||||
/** 下载报告 */
|
||||
const handleDownload = async (row: Report, format: 'pdf' | 'word') => {
|
||||
try {
|
||||
const data = await ReportApi.exportReport(row.id, format)
|
||||
const fileName = `${row.reportNo}_${row.prisonerName}.${format === 'pdf' ? 'pdf' : 'docx'}`
|
||||
if (format === 'pdf') {
|
||||
download.excel(data, fileName) // PDF 也是用 excel 方法下载(Blob 格式)
|
||||
} else {
|
||||
download.word(data, fileName)
|
||||
}
|
||||
message.success(t('common.exportSuccess'))
|
||||
} catch {
|
||||
message.error(t('common.exportFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交审核 */
|
||||
const handleSubmit = async (id: number) => {
|
||||
try {
|
||||
await message.confirm(t('prison.report.submitConfirm'))
|
||||
await ReportApi.submitReport(id)
|
||||
message.success(t('prison.report.submitSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 重新提交 */
|
||||
const handleResubmit = async (id: number) => {
|
||||
try {
|
||||
await message.confirm(t('prison.report.resubmitConfirm'))
|
||||
await ReportApi.submitReport(id)
|
||||
message.success(t('prison.report.resubmitSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await ReportApi.deleteReport(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除 */
|
||||
const handleBatchDelete = async () => {
|
||||
if (ids.value.length === 0) {
|
||||
return message.warning('请选择要删除的数据')
|
||||
}
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await ReportApi.deleteReportList(ids.value)
|
||||
message.success(t('common.delSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
await message.exportConfirm()
|
||||
exportLoading.value = true
|
||||
const data = await ReportApi.exportReportExcel(queryParams)
|
||||
download.excel(data, '评估报告.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
getTemplateList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.prisoner-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.prisoner-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.name {
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.no {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
354
src/views/prison/risk/RiskForm.vue
Normal file
354
src/views/prison/risk/RiskForm.vue
Normal file
@ -0,0 +1,354 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="罪犯编号" prop="prisonerCode">
|
||||
<el-input v-model="formData.prisonerCode" placeholder="请输入罪犯编号" :disabled="formType === 'view'" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="罪犯姓名" prop="prisonerName">
|
||||
<el-input v-model="formData.prisonerName" placeholder="请输入罪犯姓名" :disabled="formType === 'view'" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="评估人" prop="assessor">
|
||||
<el-input v-model="formData.assessor" placeholder="请输入评估人" :disabled="formType === 'view'" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="评估类型" prop="assessmentType">
|
||||
<el-select v-model="formData.assessmentType" placeholder="请选择评估类型" :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_RISK_ASSESSMENT_TYPE)"
|
||||
: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="assessmentDate">
|
||||
<el-date-picker
|
||||
v-model="formData.assessmentDate"
|
||||
type="date"
|
||||
placeholder="请选择评估日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
:disabled="formType === 'view'"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="评估方式" prop="assessMethod">
|
||||
<el-select v-model="formData.assessMethod" placeholder="请选择评估方式" :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_RISK_ASSESS_METHOD)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider content-position="left">风险评估</el-divider>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="综合得分" prop="overallScore">
|
||||
<el-input-number v-model="formData.overallScore" :min="0" :max="100" placeholder="0-100" :disabled="formType === 'view'" class="!w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="风险等级" prop="riskLevel">
|
||||
<el-select v-model="formData.riskLevel" placeholder="请选择风险等级" :disabled="formType === 'view'">
|
||||
<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="精神状态" prop="mentalState">
|
||||
<el-select v-model="formData.mentalState" placeholder="请选择精神状态" :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_RISK_MENTAL_STATE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider content-position="left">风险指标</el-divider>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="脱逃风险" prop="escapeRisk">
|
||||
<el-select v-model="formData.escapeRisk" placeholder="请选择脱逃风险等级" :disabled="formType === 'view'">
|
||||
<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="暴力倾向" prop="violenceRisk">
|
||||
<el-select v-model="formData.violenceRisk" placeholder="请选择暴力倾向等级" :disabled="formType === 'view'">
|
||||
<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="抗改风险" prop="revoltRisk">
|
||||
<el-select v-model="formData.revoltRisk" placeholder="请选择抗改风险等级" :disabled="formType === 'view'">
|
||||
<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-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="自杀自伤" prop="selfHarmRisk">
|
||||
<el-select v-model="formData.selfHarmRisk" placeholder="请选择自杀自伤等级" :disabled="formType === 'view'">
|
||||
<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-row>
|
||||
|
||||
<el-divider content-position="left">评估结果</el-divider>
|
||||
|
||||
<el-form-item label="结论" prop="conclusion">
|
||||
<el-input
|
||||
v-model="formData.conclusion"
|
||||
type="textarea"
|
||||
placeholder="请输入评估结论"
|
||||
:rows="3"
|
||||
:disabled="formType === 'view'"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="建议" prop="recommendation">
|
||||
<el-input
|
||||
v-model="formData.recommendation"
|
||||
type="textarea"
|
||||
placeholder="请输入管理建议"
|
||||
:rows="3"
|
||||
:disabled="formType === 'view'"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目得分" prop="itemScores">
|
||||
<el-input
|
||||
v-model="formData.itemScores"
|
||||
type="textarea"
|
||||
placeholder="请输入各评估项目得分,JSON格式"
|
||||
:rows="3"
|
||||
:disabled="formType === 'view'"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入备注"
|
||||
:rows="2"
|
||||
:disabled="formType === 'view'"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 查看详情时的信息展示 -->
|
||||
<template v-if="formType === 'view'">
|
||||
<el-divider content-position="left">详细信息</el-divider>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formData.createTime ? formatDateTime(formData.createTime) : '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<el-button v-if="formType !== 'view'" @click="submitForm" type="primary" :disabled="formLoading">
|
||||
{{ formType === 'create' ? '新增' : '修改' }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { formatDateTime } from '@/utils/formatTime'
|
||||
import { RiskApi, RiskSaveReqVO } from '@/api/prison/risk'
|
||||
|
||||
/** 风险评估 表单 */
|
||||
defineOptions({ name: 'PrisonRiskForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref<'create' | 'update' | 'view'>('create') // 表单的类型
|
||||
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
prisonerId: undefined,
|
||||
prisonerCode: undefined,
|
||||
prisonerName: undefined,
|
||||
assessmentType: undefined,
|
||||
assessmentDate: undefined,
|
||||
assessMethod: undefined,
|
||||
overallScore: undefined,
|
||||
riskLevel: undefined,
|
||||
mentalState: undefined,
|
||||
escapeRisk: undefined,
|
||||
violenceRisk: undefined,
|
||||
revoltRisk: undefined,
|
||||
selfHarmRisk: undefined,
|
||||
recommendation: undefined,
|
||||
assessor: undefined,
|
||||
conclusion: undefined,
|
||||
itemScores: undefined,
|
||||
remark: undefined,
|
||||
createTime: undefined
|
||||
})
|
||||
|
||||
const formRules = reactive({
|
||||
prisonerCode: [{ required: true, message: '请输入罪犯编号', trigger: 'blur' }],
|
||||
prisonerName: [{ required: true, message: '请输入罪犯姓名', trigger: 'blur' }],
|
||||
assessmentType: [{ required: true, message: '请选择评估类型', trigger: 'change' }],
|
||||
assessmentDate: [{ required: true, message: '请选择评估日期', trigger: 'change' }],
|
||||
riskLevel: [{ required: true, message: '请选择风险等级', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: 'create' | 'update' | 'view', id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = type === 'view' ? '查看详情' : t('action.' + type)
|
||||
formType.value = type
|
||||
|
||||
resetForm()
|
||||
|
||||
// 修改/查看时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await RiskApi.getRisk(id)
|
||||
formData.value = {
|
||||
id: data.id,
|
||||
prisonerId: data.prisonerId,
|
||||
prisonerCode: data.prisonerCode,
|
||||
prisonerName: data.prisonerName,
|
||||
assessmentType: data.assessmentType,
|
||||
assessmentDate: data.assessmentDate,
|
||||
assessMethod: data.assessMethod,
|
||||
overallScore: data.overallScore,
|
||||
riskLevel: data.riskLevel,
|
||||
mentalState: data.mentalState,
|
||||
escapeRisk: data.escapeRisk,
|
||||
violenceRisk: data.violenceRisk,
|
||||
revoltRisk: data.revoltRisk,
|
||||
selfHarmRisk: data.selfHarmRisk,
|
||||
recommendation: data.recommendation,
|
||||
assessor: data.assessor,
|
||||
conclusion: data.conclusion,
|
||||
itemScores: data.itemScores,
|
||||
remark: data.remark,
|
||||
createTime: data.createTime
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
if (formType.value === 'create') {
|
||||
await RiskApi.createRisk(formData.value)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await RiskApi.updateRisk(formData.value)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
prisonerId: undefined,
|
||||
prisonerCode: undefined,
|
||||
prisonerName: undefined,
|
||||
assessmentType: undefined,
|
||||
assessmentDate: undefined,
|
||||
assessMethod: undefined,
|
||||
overallScore: undefined,
|
||||
riskLevel: undefined,
|
||||
mentalState: undefined,
|
||||
escapeRisk: undefined,
|
||||
violenceRisk: undefined,
|
||||
revoltRisk: undefined,
|
||||
selfHarmRisk: undefined,
|
||||
recommendation: undefined,
|
||||
assessor: undefined,
|
||||
conclusion: undefined,
|
||||
itemScores: undefined,
|
||||
remark: undefined,
|
||||
createTime: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
295
src/views/prison/risk/index.vue
Normal file
295
src/views/prison/risk/index.vue
Normal file
@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="90px"
|
||||
>
|
||||
<el-form-item label="罪犯编号" prop="prisonerCode">
|
||||
<el-input
|
||||
v-model="queryParams.prisonerCode"
|
||||
placeholder="请输入罪犯编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-140px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯姓名" prop="prisonerName">
|
||||
<el-input
|
||||
v-model="queryParams.prisonerName"
|
||||
placeholder="请输入罪犯姓名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-140px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="评估类型" prop="assessmentType">
|
||||
<el-select
|
||||
v-model="queryParams.assessmentType"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-120px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in assessmentTypeOptions"
|
||||
: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-100px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in riskLevelOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="评估人" prop="assessor">
|
||||
<el-input
|
||||
v-model="queryParams.assessor"
|
||||
placeholder="请输入评估人"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-120px"
|
||||
/>
|
||||
</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:risk:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增评估
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:risk:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="编号" align="center" prop="id" width="80" />
|
||||
<el-table-column label="罪犯编号" align="center" prop="prisonerCode" width="100" />
|
||||
<el-table-column label="罪犯姓名" align="center" prop="prisonerName" width="100" />
|
||||
<el-table-column label="评估类型" align="center" prop="assessmentType" width="120">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_RISK_ASSESSMENT_TYPE" :value="row.assessmentType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="评估日期" align="center" prop="assessmentDate" width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.assessmentDate">{{ formatDate(row.assessmentDate) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="综合得分" align="center" prop="overallScore" width="100">
|
||||
<template #default="{ row }">
|
||||
<span :class="getScoreClass(row.overallScore)">{{ row.overallScore ?? '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="风险等级" align="center" prop="riskLevel" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_RISK_LEVEL" :value="row.riskLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="评估人" align="center" prop="assessor" width="100" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.createTime">{{ formatDateTime(row.createTime) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('view', row.id)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> 查看
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', row.id)"
|
||||
v-hasPermi="['prison:risk:update']"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> 修改
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
v-hasPermi="['prison:risk:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> 删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<RiskForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { formatDateTime, formatDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { RiskApi, RiskPageReqVO } from '@/api/prison/risk'
|
||||
import RiskForm from './RiskForm.vue'
|
||||
|
||||
defineOptions({ name: 'PrisonRisk' })
|
||||
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const list = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
const selectedIds = ref<number[]>([])
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive<RiskPageReqVO>({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
prisonerCode: undefined,
|
||||
prisonerName: undefined,
|
||||
assessmentType: undefined,
|
||||
riskLevel: undefined,
|
||||
assessor: undefined,
|
||||
assessmentDate: undefined
|
||||
})
|
||||
const queryFormRef = ref()
|
||||
|
||||
// 字典数据
|
||||
const assessmentTypeOptions = getIntDictOptions(DICT_TYPE.PRISON_ASSESSMENT_TYPE)
|
||||
const riskLevelOptions = getIntDictOptions(DICT_TYPE.PRISON_RISK_LEVEL)
|
||||
|
||||
/** 根据得分返回样式类名 */
|
||||
const getScoreClass = (score: number | undefined) => {
|
||||
if (score === undefined || score === null) return ''
|
||||
if (score >= 80) return 'text-success'
|
||||
if (score >= 60) return 'text-warning'
|
||||
return 'text-danger'
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await RiskApi.getRiskPage(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 handleSelectionChange = (selection: any[]) => {
|
||||
selectedIds.value = selection.map((item) => item.id)
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await RiskApi.deleteRisk(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
await message.exportConfirm()
|
||||
exportLoading.value = true
|
||||
const data = await RiskApi.exportRisk(queryParams)
|
||||
download.excel(data, '风险评估.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-success {
|
||||
color: #67c23a;
|
||||
font-weight: bold;
|
||||
}
|
||||
.text-warning {
|
||||
color: #e6a23c;
|
||||
font-weight: bold;
|
||||
}
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@ -7,11 +7,24 @@
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="罪犯ID" prop="prisonerId">
|
||||
<el-input v-model="formData.prisonerId" placeholder="请输入罪犯ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯编号" prop="prisonerNo">
|
||||
<el-input v-model="formData.prisonerNo" placeholder="请输入罪犯编号" />
|
||||
<el-form-item label="罪犯" prop="prisonerId">
|
||||
<el-select
|
||||
v-model="formData.prisonerId"
|
||||
placeholder="请选择罪犯"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="searchPrisoner"
|
||||
:loading="prisonerLoading"
|
||||
@change="handlePrisonerChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="prisoner in prisonerList"
|
||||
:key="prisoner.id"
|
||||
:label="`${prisoner.name} (${prisoner.prisonerNo})`"
|
||||
:value="prisoner.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="评估类型" prop="assessmentType">
|
||||
<el-select v-model="formData.assessmentType" placeholder="请选择评估类型">
|
||||
@ -95,6 +108,7 @@
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { RiskAssessmentApi, RiskAssessment } from '@/api/prison/riskassessment'
|
||||
import { PrisonerApi, PrisonerVO } from '@/api/prison/prisoner'
|
||||
|
||||
/** 危险评估 表单 */
|
||||
defineOptions({ name: 'RiskAssessmentForm' })
|
||||
@ -106,35 +120,69 @@ const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
|
||||
// 罪犯选择相关
|
||||
const prisonerLoading = ref(false)
|
||||
const prisonerList = ref<PrisonerVO[]>([])
|
||||
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
prisonerId: undefined,
|
||||
prisonerNo: undefined,
|
||||
assessmentType: undefined,
|
||||
assessmentDate: undefined,
|
||||
violenceScore: undefined,
|
||||
escapeScore: undefined,
|
||||
suicideScore: undefined,
|
||||
totalScore: undefined,
|
||||
riskLevel: undefined,
|
||||
riskFactors: undefined,
|
||||
suggestions: undefined,
|
||||
assessorId: undefined,
|
||||
assessorName: undefined,
|
||||
nextAssessmentDate: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
prisonerId: undefined as number | undefined,
|
||||
prisonerNo: undefined as string | undefined,
|
||||
assessmentType: undefined as number | undefined,
|
||||
assessmentDate: undefined as number | undefined,
|
||||
violenceScore: undefined as number | undefined,
|
||||
escapeScore: undefined as number | undefined,
|
||||
suicideScore: undefined as number | undefined,
|
||||
totalScore: undefined as number | undefined,
|
||||
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,
|
||||
status: 1 as number | undefined,
|
||||
remark: undefined as string | undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
prisonerId: [{ required: true, message: '罪犯ID不能为空', trigger: 'blur' }],
|
||||
prisonerNo: [{ required: true, message: '罪犯编号不能为空', trigger: 'blur' }],
|
||||
prisonerId: [{ required: true, message: '罪犯不能为空', trigger: 'change' }],
|
||||
assessmentType: [{ required: true, message: '评估类型不能为空', trigger: 'change' }],
|
||||
assessmentDate: [{ required: true, message: '评估日期不能为空', trigger: 'blur' }],
|
||||
assessmentDate: [{ required: true, message: '评估日期不能为空', trigger: 'change' }],
|
||||
riskLevel: [{ required: true, message: '风险等级不能为空', trigger: 'change' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 搜索罪犯 */
|
||||
const searchPrisoner = async (query: string) => {
|
||||
if (!query) {
|
||||
prisonerList.value = []
|
||||
return
|
||||
}
|
||||
prisonerLoading.value = true
|
||||
try {
|
||||
const data = await PrisonerApi.getPage({
|
||||
pageNo: 1,
|
||||
pageSize: 20,
|
||||
name: query // 按姓名搜索
|
||||
} as any)
|
||||
prisonerList.value = data.list || []
|
||||
} catch (error) {
|
||||
console.error('搜索罪犯失败:', error)
|
||||
prisonerList.value = []
|
||||
} finally {
|
||||
prisonerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 罪犯选择变化 */
|
||||
const handlePrisonerChange = async (prisonerId: number) => {
|
||||
const prisoner = prisonerList.value.find(p => p.id === prisonerId)
|
||||
if (prisoner) {
|
||||
formData.value.prisonerNo = prisoner.prisonerNo
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
@ -146,6 +194,11 @@ const open = async (type: string, id?: number) => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await RiskAssessmentApi.getRiskAssessment(id)
|
||||
// 加载已选罪犯信息
|
||||
if (formData.value.prisonerId) {
|
||||
const prisoner = await PrisonerApi.getPrisoner(formData.value.prisonerId)
|
||||
prisonerList.value = [prisoner]
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
@ -195,9 +248,10 @@ const resetForm = () => {
|
||||
assessorId: undefined,
|
||||
assessorName: undefined,
|
||||
nextAssessmentDate: undefined,
|
||||
status: undefined,
|
||||
status: 1,
|
||||
remark: undefined
|
||||
}
|
||||
prisonerList.value = []
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@ -14,7 +14,16 @@
|
||||
placeholder="请输入罪犯编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-140px"
|
||||
class="!w-120px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯姓名" prop="prisonerName">
|
||||
<el-input
|
||||
v-model="queryParams.prisonerName"
|
||||
placeholder="请输入罪犯姓名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-120px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="评估类型" prop="assessmentType">
|
||||
@ -105,12 +114,17 @@
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="评估ID" align="center" prop="id" width="80" />
|
||||
<el-table-column label="罪犯编号" align="center" prop="prisonerNo" width="120" />
|
||||
<el-table-column label="罪犯姓名" align="center" prop="prisonerName" width="100" />
|
||||
<el-table-column label="评估类型" align="center" prop="assessmentType" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_ASSESSMENT_TYPE" :value="scope.row.assessmentType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="评估日期" align="center" prop="assessmentDate" width="120" />
|
||||
<el-table-column label="评估日期" align="center" prop="assessmentDate" width="120">
|
||||
<template #default="scope">
|
||||
{{ formatDateTime(scope.row.assessmentDate) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="暴力得分" align="center" prop="violenceScore" width="90" />
|
||||
<el-table-column label="脱逃得分" align="center" prop="escapeScore" width="90" />
|
||||
<el-table-column label="自杀得分" align="center" prop="suicideScore" width="90" />
|
||||
@ -129,10 +143,10 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ dateFormatter(scope.row.createTime) }}
|
||||
{{ formatDateTime(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="120">
|
||||
<el-table-column label="操作" align="center" width="120" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
@ -167,7 +181,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { formatDateTime } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { RiskAssessmentApi, RiskAssessment } from '@/api/prison/riskassessment'
|
||||
import RiskAssessmentForm from './RiskAssessmentForm.vue'
|
||||
@ -184,6 +198,7 @@ const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
prisonerNo: undefined,
|
||||
prisonerName: undefined,
|
||||
assessmentType: undefined,
|
||||
riskLevel: undefined,
|
||||
status: undefined
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>考核规则配置</span>
|
||||
<el-button type="primary" @click="handleCreate" v-hasPermi="'prison:score-rule:create'">
|
||||
<el-button type="primary" @click="handleCreate" v-hasPermi="['prison:score-rule:create']">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增规则
|
||||
</el-button>
|
||||
</div>
|
||||
@ -78,10 +78,10 @@
|
||||
<el-table-column prop="sort" label="排序" width="80" />
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="handleEdit(row)" v-hasPermi="'prison:score-rule:update'">
|
||||
<el-button link type="primary" @click="handleEdit(row)" v-hasPermi="['prison:score-rule:update']">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button link type="danger" @click="handleDelete(row)" v-hasPermi="'prison:score-rule:delete'">
|
||||
<el-button link type="danger" @click="handleDelete(row)" v-hasPermi="['prison:score-rule:delete']">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
@ -7,32 +7,58 @@
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="罪犯ID" prop="prisonerId">
|
||||
<el-input v-model="formData.prisonerId" placeholder="请输入罪犯ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯编号" prop="prisonerNo">
|
||||
<el-input v-model="formData.prisonerNo" placeholder="请输入罪犯编号" />
|
||||
<el-form-item label="罪犯" prop="prisonerId">
|
||||
<el-select
|
||||
v-model="formData.prisonerId"
|
||||
placeholder="请选择罪犯"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="searchPrisoner"
|
||||
:loading="prisonerLoading"
|
||||
@change="handlePrisonerChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="prisoner in prisonerList"
|
||||
:key="prisoner.id"
|
||||
:label="`${prisoner.name} (${prisoner.prisonerNo})`"
|
||||
:value="prisoner.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="考核年份" prop="year">
|
||||
<el-input v-model="formData.year" placeholder="请输入考核年份" />
|
||||
<el-date-picker
|
||||
v-model="formData.year"
|
||||
type="year"
|
||||
placeholder="请选择考核年份"
|
||||
value-format="YYYY"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="考核月份" prop="month">
|
||||
<el-input v-model="formData.month" placeholder="请输入考核月份" />
|
||||
<el-select v-model="formData.month" placeholder="请选择考核月份" style="width: 100%">
|
||||
<el-option
|
||||
v-for="month in 12"
|
||||
:key="month"
|
||||
:label="`${month}月`"
|
||||
:value="month"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="基础分" prop="baseScore">
|
||||
<el-input v-model="formData.baseScore" placeholder="请输入基础分" />
|
||||
<el-input-number v-model="formData.baseScore" :min="0" :precision="2" placeholder="请输入基础分" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="加分" prop="rewardScore">
|
||||
<el-input v-model="formData.rewardScore" placeholder="请输入加分" />
|
||||
<el-input-number v-model="formData.rewardScore" :min="0" :precision="2" placeholder="请输入加分" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="扣分" prop="penaltyScore">
|
||||
<el-input v-model="formData.penaltyScore" placeholder="请输入扣分" />
|
||||
<el-input-number v-model="formData.penaltyScore" :min="0" :precision="2" placeholder="请输入扣分" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="总分" prop="totalScore">
|
||||
<el-input v-model="formData.totalScore" placeholder="请输入总分" />
|
||||
<el-input-number v-model="formData.totalScore" :min="0" :precision="2" placeholder="请输入总分" controls-position="right" style="width: 100%" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="考核等级" prop="level">
|
||||
<el-select v-model="formData.level" placeholder="请选择考核等级">
|
||||
<el-select v-model="formData.level" placeholder="请选择考核等级" style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_SCORE_LEVEL)"
|
||||
:key="dict.value"
|
||||
@ -41,12 +67,6 @@
|
||||
/>
|
||||
</el-select>
|
||||
</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="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
@ -69,6 +89,7 @@
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { ScoreApi, Score } from '@/api/prison/score'
|
||||
import { PrisonerApi, PrisonerVO } from '@/api/prison/prisoner'
|
||||
|
||||
/** 计分考核 表单 */
|
||||
defineOptions({ name: 'ScoreForm' })
|
||||
@ -80,31 +101,65 @@ const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
|
||||
// 罪犯选择相关
|
||||
const prisonerLoading = ref(false)
|
||||
const prisonerList = ref<PrisonerVO[]>([])
|
||||
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
prisonerId: undefined,
|
||||
prisonerNo: undefined,
|
||||
year: undefined,
|
||||
month: undefined,
|
||||
baseScore: undefined,
|
||||
rewardScore: undefined,
|
||||
penaltyScore: undefined,
|
||||
totalScore: undefined,
|
||||
level: undefined,
|
||||
assessorId: undefined,
|
||||
assessorName: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
prisonerId: undefined as number | undefined,
|
||||
prisonerNo: undefined as string | undefined,
|
||||
year: undefined as string | undefined,
|
||||
month: undefined as number | undefined,
|
||||
baseScore: 0,
|
||||
rewardScore: 0,
|
||||
penaltyScore: 0,
|
||||
totalScore: 0,
|
||||
level: undefined as number | undefined,
|
||||
assessorId: undefined as number | undefined,
|
||||
assessorName: undefined as string | undefined,
|
||||
status: 1,
|
||||
remark: undefined as string | undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
prisonerId: [{ required: true, message: '罪犯ID不能为空', trigger: 'blur' }],
|
||||
prisonerNo: [{ required: true, message: '罪犯编号不能为空', trigger: 'blur' }],
|
||||
year: [{ required: true, message: '考核年份不能为空', trigger: 'blur' }],
|
||||
month: [{ required: true, message: '考核月份不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
prisonerId: [{ required: true, message: '罪犯不能为空', trigger: 'change' }],
|
||||
year: [{ required: true, message: '考核年份不能为空', trigger: 'change' }],
|
||||
month: [{ required: true, message: '考核月份不能为空', trigger: 'change' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 搜索罪犯 */
|
||||
const searchPrisoner = async (query: string) => {
|
||||
if (!query) {
|
||||
prisonerList.value = []
|
||||
return
|
||||
}
|
||||
prisonerLoading.value = true
|
||||
try {
|
||||
const data = await PrisonerApi.getPage({
|
||||
pageNo: 1,
|
||||
pageSize: 20,
|
||||
name: query // 按姓名搜索
|
||||
} as any)
|
||||
prisonerList.value = data.list || []
|
||||
} catch (error) {
|
||||
console.error('搜索罪犯失败:', error)
|
||||
prisonerList.value = []
|
||||
} finally {
|
||||
prisonerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 罪犯选择变化 */
|
||||
const handlePrisonerChange = async (prisonerId: number) => {
|
||||
const prisoner = prisonerList.value.find(p => p.id === prisonerId)
|
||||
if (prisoner) {
|
||||
formData.value.prisonerNo = prisoner.prisonerNo
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
@ -115,7 +170,13 @@ const open = async (type: string, id?: number) => {
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ScoreApi.getScore(id)
|
||||
const data = await ScoreApi.getScore(id)
|
||||
formData.value = { ...data, year: data.year?.toString() } as any
|
||||
// 加载已选罪犯信息
|
||||
if (data.prisonerId) {
|
||||
const prisoner = await PrisonerApi.getPrisoner(data.prisonerId)
|
||||
prisonerList.value = [prisoner]
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
@ -155,16 +216,17 @@ const resetForm = () => {
|
||||
prisonerNo: undefined,
|
||||
year: undefined,
|
||||
month: undefined,
|
||||
baseScore: undefined,
|
||||
rewardScore: undefined,
|
||||
penaltyScore: undefined,
|
||||
totalScore: undefined,
|
||||
baseScore: 0,
|
||||
rewardScore: 0,
|
||||
penaltyScore: 0,
|
||||
totalScore: 0,
|
||||
level: undefined,
|
||||
assessorId: undefined,
|
||||
assessorName: undefined,
|
||||
status: undefined,
|
||||
status: 1,
|
||||
remark: undefined
|
||||
}
|
||||
prisonerList.value = []
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@ -17,6 +17,15 @@
|
||||
class="!w-140px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯姓名" prop="prisonerName">
|
||||
<el-input
|
||||
v-model="queryParams.prisonerName"
|
||||
placeholder="请输入罪犯姓名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-120px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="考核年份" prop="year">
|
||||
<el-input
|
||||
v-model="queryParams.year"
|
||||
@ -98,7 +107,10 @@
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="记录ID" align="center" prop="id" width="80" />
|
||||
<el-table-column label="罪犯姓名" align="center" prop="prisonerName" width="100" />
|
||||
<el-table-column label="罪犯编号" align="center" prop="prisonerNo" width="120" />
|
||||
<el-table-column label="监区" align="center" prop="prisonAreaName" width="100" />
|
||||
<el-table-column label="监室" align="center" prop="prisonCellName" width="100" />
|
||||
<el-table-column label="年份" align="center" prop="year" width="80" />
|
||||
<el-table-column label="月份" align="center" prop="month" width="70" />
|
||||
<el-table-column label="基础分" align="center" prop="baseScore" width="80" />
|
||||
@ -118,10 +130,10 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ dateFormatter(scope.row.createTime) }}
|
||||
{{ formatDateTime(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="120">
|
||||
<el-table-column label="操作" align="center" width="120" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
@ -156,7 +168,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { formatDateTime } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { ScoreApi, Score } from '@/api/prison/score'
|
||||
import ScoreForm from './ScoreForm.vue'
|
||||
@ -173,6 +185,7 @@ const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
prisonerNo: undefined,
|
||||
prisonerName: undefined,
|
||||
year: undefined,
|
||||
level: undefined,
|
||||
status: undefined
|
||||
|
||||
340
src/views/prison/situation/SituationForm.vue
Normal file
340
src/views/prison/situation/SituationForm.vue
Normal file
@ -0,0 +1,340 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="700px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="formData.title" placeholder="请输入标题" :disabled="formType === 'view'" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="分类" prop="category">
|
||||
<el-select v-model="formData.category" placeholder="请选择分类" :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_SITUATION_CATEGORY)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="等级" prop="level">
|
||||
<el-select v-model="formData.level" placeholder="请选择等级" :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_SITUATION_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="12">
|
||||
<el-form-item label="来源" prop="source">
|
||||
<el-select v-model="formData.source" placeholder="请选择来源" :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_SITUATION_SOURCE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="formData.status" placeholder="请选择状态" :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_SITUATION_STATUS)"
|
||||
: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="12">
|
||||
<el-form-item label="监区" prop="areaId">
|
||||
<el-tree-select
|
||||
v-model="formData.areaId"
|
||||
:data="areaTreeList"
|
||||
:props="{ label: 'name', value: 'id', children: 'children' }"
|
||||
placeholder="请选择监区"
|
||||
clearable
|
||||
filterable
|
||||
:disabled="formType === 'view'"
|
||||
:render-after-expand="false"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="监室" prop="cellId">
|
||||
<el-select v-model="formData.cellId" placeholder="请选择监室" clearable :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="cell in cellOptions"
|
||||
:key="cell.id"
|
||||
:label="cell.name"
|
||||
:value="cell.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="报告人" prop="reporter">
|
||||
<el-input v-model="formData.reporter" placeholder="请输入报告人" :disabled="formType === 'view'" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="处理人" prop="handler">
|
||||
<el-input v-model="formData.handler" placeholder="请输入处理人" :disabled="formType === 'view'" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="发生时间" prop="occurTime">
|
||||
<el-date-picker
|
||||
v-model="formData.occurTime"
|
||||
type="datetime"
|
||||
placeholder="请选择发生时间"
|
||||
value-format="x"
|
||||
:disabled="formType === 'view'"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="详情内容" prop="content">
|
||||
<el-input
|
||||
v-model="formData.content"
|
||||
type="textarea"
|
||||
placeholder="请输入详情内容"
|
||||
:rows="4"
|
||||
:disabled="formType === 'view'"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入备注"
|
||||
:rows="3"
|
||||
:disabled="formType === 'view'"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 查看详情时的信息展示 -->
|
||||
<template v-if="formType === 'view'">
|
||||
<el-divider content-position="left">详细信息</el-divider>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="监区">
|
||||
{{ formData.areaName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="监室">
|
||||
{{ formData.cellName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formData.createTime ? formatDateTime(formData.createTime) : '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<el-button v-if="formType !== 'view'" @click="submitForm" type="primary" :disabled="formLoading">
|
||||
{{ formType === 'create' ? '新增' : '修改' }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { formatDateTime } from '@/utils/formatTime'
|
||||
import { SituationApi, SituationSaveReqVO } from '@/api/prison/situation'
|
||||
|
||||
/** 狱情收集 表单 */
|
||||
defineOptions({ name: 'PrisonSituationForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref<'create' | 'update' | 'view'>('create') // 表单的类型
|
||||
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
title: undefined,
|
||||
content: undefined,
|
||||
category: 1,
|
||||
level: 1,
|
||||
source: 1,
|
||||
status: 1,
|
||||
areaId: undefined,
|
||||
cellId: undefined,
|
||||
reporter: undefined,
|
||||
handler: undefined,
|
||||
occurTime: undefined,
|
||||
remark: undefined,
|
||||
areaName: undefined,
|
||||
cellName: undefined,
|
||||
createTime: undefined
|
||||
})
|
||||
|
||||
const formRules = reactive({
|
||||
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
|
||||
category: [{ required: true, message: '请选择分类', trigger: 'change' }],
|
||||
level: [{ required: true, message: '请选择等级', trigger: 'change' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const areaTreeList = ref<any[]>([]) // 监区树形列表
|
||||
const cellOptions = ref<{ id: number; name: string }[]>([]) // 监室选项
|
||||
|
||||
/** 获取监区树形列表 */
|
||||
const loadAreaTree = async () => {
|
||||
areaTreeList.value = await SituationApi.AreaApi.getAreaTree()
|
||||
}
|
||||
|
||||
/** 获取监室列表 */
|
||||
const loadCellList = async (areaId?: number) => {
|
||||
try {
|
||||
const data = await SituationApi.getCellList({ areaId, status: 1 })
|
||||
cellOptions.value = data
|
||||
} catch {
|
||||
cellOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听监区变化 */
|
||||
const handleAreaChange = (areaId: number | undefined) => {
|
||||
formData.value.cellId = undefined
|
||||
if (areaId) {
|
||||
loadCellList(areaId)
|
||||
} else {
|
||||
cellOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: 'create' | 'update' | 'view', id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = type === 'view' ? '查看详情' : t('action.' + type)
|
||||
formType.value = type
|
||||
|
||||
// 加载选项数据
|
||||
await loadAreaTree()
|
||||
|
||||
resetForm()
|
||||
|
||||
// 修改/查看时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await SituationApi.getSituation(id)
|
||||
formData.value = {
|
||||
id: data.id,
|
||||
title: data.title,
|
||||
content: data.content,
|
||||
category: data.category,
|
||||
level: data.level,
|
||||
source: data.source,
|
||||
status: data.status,
|
||||
areaId: data.areaId,
|
||||
cellId: data.cellId,
|
||||
reporter: data.reporter,
|
||||
handler: data.handler,
|
||||
occurTime: data.occurTime ? new Date(data.occurTime).getTime() : undefined,
|
||||
remark: data.remark,
|
||||
areaName: data.areaName,
|
||||
cellName: data.cellName,
|
||||
createTime: data.createTime
|
||||
}
|
||||
// 如果有监区,加载对应的监室列表
|
||||
if (data.areaId) {
|
||||
await loadCellList(data.areaId)
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = {
|
||||
...formData.value,
|
||||
occurTime: formData.value.occurTime ? new Date(formData.value.occurTime).toISOString() : undefined
|
||||
} as unknown as SituationSaveReqVO
|
||||
if (formType.value === 'create') {
|
||||
await SituationApi.createSituation(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await SituationApi.updateSituation(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
title: undefined,
|
||||
content: undefined,
|
||||
category: 1,
|
||||
level: 1,
|
||||
source: 1,
|
||||
status: 1,
|
||||
areaId: undefined,
|
||||
cellId: undefined,
|
||||
reporter: undefined,
|
||||
handler: undefined,
|
||||
occurTime: undefined,
|
||||
remark: undefined,
|
||||
areaName: undefined,
|
||||
cellName: undefined,
|
||||
createTime: undefined
|
||||
}
|
||||
cellOptions.value = []
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// 监听监区变化
|
||||
watch(() => formData.value.areaId, (val) => {
|
||||
handleAreaChange(val)
|
||||
})
|
||||
</script>
|
||||
315
src/views/prison/situation/index.vue
Normal file
315
src/views/prison/situation/index.vue
Normal file
@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input
|
||||
v-model="queryParams.title"
|
||||
placeholder="请输入标题"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-160px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-120px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in categoryOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="等级" prop="level">
|
||||
<el-select
|
||||
v-model="queryParams.level"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-100px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in levelOptions"
|
||||
: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-100px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in statusOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="监区" prop="areaId">
|
||||
<el-tree-select
|
||||
v-model="queryParams.areaId"
|
||||
:data="areaTreeList"
|
||||
:props="{ label: 'name', value: 'id', children: 'children' }"
|
||||
placeholder="请选择监区"
|
||||
clearable
|
||||
filterable
|
||||
class="!w-180px"
|
||||
:render-after-expand="false"
|
||||
/>
|
||||
</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:situation:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增狱情
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:situation:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="编号" align="center" prop="id" width="80" />
|
||||
<el-table-column label="标题" align="center" prop="title" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="分类" align="center" prop="category" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_SITUATION_CATEGORY" :value="row.category" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="等级" align="center" prop="level" width="90">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_SITUATION_LEVEL" :value="row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来源" align="center" prop="source" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_SITUATION_SOURCE" :value="row.source" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_SITUATION_STATUS" :value="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="监区" align="center" prop="areaName" width="120" />
|
||||
<el-table-column label="报告人" align="center" prop="reporter" width="100" />
|
||||
<el-table-column label="发生时间" align="center" prop="occurTime" width="160">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.occurTime">{{ formatDateTime(row.occurTime) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.createTime">{{ formatDateTime(row.createTime) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('view', row.id)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> 查看
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', row.id)"
|
||||
v-hasPermi="['prison:situation:update']"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> 修改
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
v-hasPermi="['prison:situation:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> 删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<SituationForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { formatDateTime } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { SituationApi, SituationPageReqVO } from '@/api/prison/situation'
|
||||
import SituationForm from './SituationForm.vue'
|
||||
|
||||
defineOptions({ name: 'PrisonSituation' })
|
||||
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const list = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
const selectedIds = ref<number[]>([])
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive<SituationPageReqVO>({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
title: undefined,
|
||||
category: undefined,
|
||||
level: undefined,
|
||||
source: undefined,
|
||||
status: undefined,
|
||||
areaId: undefined,
|
||||
cellId: undefined,
|
||||
reporter: undefined
|
||||
})
|
||||
const queryFormRef = ref()
|
||||
|
||||
// 字典数据
|
||||
const categoryOptions = getIntDictOptions(DICT_TYPE.PRISON_SITUATION_CATEGORY)
|
||||
const levelOptions = getIntDictOptions(DICT_TYPE.PRISON_SITUATION_LEVEL)
|
||||
const sourceOptions = getIntDictOptions(DICT_TYPE.PRISON_SITUATION_SOURCE)
|
||||
const statusOptions = getIntDictOptions(DICT_TYPE.PRISON_SITUATION_STATUS)
|
||||
|
||||
// 监区选项
|
||||
const areaOptions = ref<{ id: number; name: string }[]>([])
|
||||
// 监区树形列表
|
||||
const areaTreeList = ref<any[]>([])
|
||||
|
||||
/** 获取监区树形列表 */
|
||||
const loadAreas = async () => {
|
||||
areaTreeList.value = await SituationApi.AreaApi.getAreaTree()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await SituationApi.getSituationPage(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 handleSelectionChange = (selection: any[]) => {
|
||||
selectedIds.value = selection.map((item) => item.id)
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await SituationApi.deleteSituation(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除 */
|
||||
const handleBatchDelete = async () => {
|
||||
if (selectedIds.value.length === 0) {
|
||||
message.warning('请选择要删除的数据')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await SituationApi.deleteSituationList(selectedIds.value)
|
||||
message.success(t('common.delSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
await message.exportConfirm()
|
||||
exportLoading.value = true
|
||||
const data = await SituationApi.exportSituation(queryParams)
|
||||
download.excel(data, '狱情收集.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
loadAreas()
|
||||
})
|
||||
</script>
|
||||
134
src/views/prison/warning/WarningActionForm.vue
Normal file
134
src/views/prison/warning/WarningActionForm.vue
Normal file
@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<Dialog :title="actionTitle" v-model="dialogVisible" width="500px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item :label="actionLabel" :prop="actionProp">
|
||||
<el-input
|
||||
v-model="formData.result"
|
||||
type="textarea"
|
||||
:placeholder="`请输入${actionLabel}`"
|
||||
:rows="4"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">
|
||||
确认{{ actionBtnText }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { WarningApi } from '@/api/prison/warning'
|
||||
|
||||
/** 预警处置操作表单 */
|
||||
defineOptions({ name: 'PrisonWarningActionForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const warningId = ref<number>() // 当前操作的预警ID
|
||||
const actionType = ref<'verify' | 'handle' | 'release'>('verify') // 操作类型
|
||||
|
||||
const formData = ref({
|
||||
result: ''
|
||||
})
|
||||
|
||||
const formRules = reactive({
|
||||
result: [{ required: true, message: '请输入内容', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
// 根据操作类型返回标题
|
||||
const actionTitle = computed(() => {
|
||||
const titles = {
|
||||
verify: '预警核实',
|
||||
handle: '预警处置',
|
||||
release: '预警解除'
|
||||
}
|
||||
return titles[actionType.value]
|
||||
})
|
||||
|
||||
// 根据操作类型返回标签
|
||||
const actionLabel = computed(() => {
|
||||
const labels = {
|
||||
verify: '核实结果',
|
||||
handle: '处置结果',
|
||||
release: '解除原因'
|
||||
}
|
||||
return labels[actionType.value]
|
||||
})
|
||||
|
||||
// 根据操作类型返回按钮文字
|
||||
const actionBtnText = computed(() => {
|
||||
const texts = {
|
||||
verify: '核实',
|
||||
handle: '处置',
|
||||
release: '解除'
|
||||
}
|
||||
return texts[actionType.value]
|
||||
})
|
||||
|
||||
// 根据操作类型返回API方法和prop
|
||||
const actionProp = computed(() => {
|
||||
const props = {
|
||||
verify: 'verifyResult',
|
||||
handle: 'handleResult',
|
||||
release: 'releaseReason'
|
||||
}
|
||||
return props[actionType.value]
|
||||
})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = (type: 'verify' | 'handle' | 'release', id: number) => {
|
||||
dialogVisible.value = true
|
||||
actionType.value = type
|
||||
warningId.value = id
|
||||
formData.value.result = ''
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data: any = {
|
||||
id: warningId.value
|
||||
}
|
||||
data[actionProp.value] = formData.value.result
|
||||
|
||||
if (actionType.value === 'verify') {
|
||||
await WarningApi.verifyWarning(data)
|
||||
message.success('核实成功')
|
||||
} else if (actionType.value === 'handle') {
|
||||
await WarningApi.handleWarning(data)
|
||||
message.success('处置成功')
|
||||
} else if (actionType.value === 'release') {
|
||||
await WarningApi.releaseWarning(data)
|
||||
message.success('解除成功')
|
||||
}
|
||||
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
444
src/views/prison/warning/WarningForm.vue
Normal file
444
src/views/prison/warning/WarningForm.vue
Normal file
@ -0,0 +1,444 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="700px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="formData.title" placeholder="请输入标题" :disabled="formType === 'view'" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-select v-model="formData.type" placeholder="请选择类型" :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_WARNING_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="等级" prop="level">
|
||||
<el-select v-model="formData.level" placeholder="请选择等级" :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_WARNING_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="12">
|
||||
<el-form-item label="来源" prop="source">
|
||||
<el-select v-model="formData.source" placeholder="请选择来源" :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_WARNING_SOURCE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="formData.status" placeholder="请选择状态" :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_WARNING_STATUS)"
|
||||
: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="12">
|
||||
<el-form-item label="关联狱情" prop="situationId">
|
||||
<el-select v-model="formData.situationId" placeholder="请选择关联狱情" clearable :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="sit in situationOptions"
|
||||
:key="sit.id"
|
||||
:label="sit.title"
|
||||
:value="sit.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="发生时间" prop="occurTime">
|
||||
<el-date-picker
|
||||
v-model="formData.occurTime"
|
||||
type="datetime"
|
||||
placeholder="请选择发生时间"
|
||||
value-format="x"
|
||||
:disabled="formType === 'view'"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="监区" prop="areaId">
|
||||
<el-tree-select
|
||||
v-model="formData.areaId"
|
||||
:data="areaTreeList"
|
||||
:props="{ label: 'name', value: 'id', children: 'children' }"
|
||||
placeholder="请选择监区"
|
||||
clearable
|
||||
filterable
|
||||
:disabled="formType === 'view'"
|
||||
:render-after-expand="false"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="监室" prop="cellId">
|
||||
<el-select v-model="formData.cellId" placeholder="请选择监室" clearable :disabled="formType === 'view'">
|
||||
<el-option
|
||||
v-for="cell in cellOptions"
|
||||
:key="cell.id"
|
||||
:label="cell.name"
|
||||
:value="cell.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="预警时间" prop="alertTime">
|
||||
<el-date-picker
|
||||
v-model="formData.alertTime"
|
||||
type="datetime"
|
||||
placeholder="请选择预警时间"
|
||||
value-format="x"
|
||||
:disabled="formType === 'view'"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="内容" prop="content">
|
||||
<el-input
|
||||
v-model="formData.content"
|
||||
type="textarea"
|
||||
placeholder="请输入内容"
|
||||
:rows="4"
|
||||
:disabled="formType === 'view'"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入备注"
|
||||
:rows="3"
|
||||
:disabled="formType === 'view'"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 查看详情时的完整信息展示 -->
|
||||
<template v-if="formType === 'view'">
|
||||
<el-divider content-position="left">详细信息</el-divider>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="监区">
|
||||
{{ formData.areaName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="监室">
|
||||
{{ formData.cellName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="关联狱情">
|
||||
{{ formData.situationTitle || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formData.createTime ? formatDateTime(formData.createTime) : '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 核实信息 -->
|
||||
<el-divider content-position="left">核实信息</el-divider>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="核实人">
|
||||
{{ formData.verifier || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="核实时间">
|
||||
{{ formData.verifyTime ? formatDateTime(formData.verifyTime) : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="核实结果" :span="2">
|
||||
{{ formData.verifyResult || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 处置信息 -->
|
||||
<el-divider content-position="left">处置信息</el-divider>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="处置人">
|
||||
{{ formData.handler || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="处置时间">
|
||||
{{ formData.handleTime ? formatDateTime(formData.handleTime) : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="处置结果" :span="2">
|
||||
{{ formData.handleResult || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 解除信息 -->
|
||||
<el-divider content-position="left">解除信息</el-divider>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="解除人">
|
||||
{{ formData.releaser || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="解除时间">
|
||||
{{ formData.releaseTime ? formatDateTime(formData.releaseTime) : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="解除原因" :span="2">
|
||||
{{ formData.releaseReason || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<el-button v-if="formType !== 'view'" @click="submitForm" type="primary" :disabled="formLoading">
|
||||
{{ formType === 'create' ? '新增' : '修改' }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { formatDateTime } from '@/utils/formatTime'
|
||||
import { WarningApi, WarningSaveReqVO } from '@/api/prison/warning'
|
||||
|
||||
/** 预警管理 表单 */
|
||||
defineOptions({ name: 'PrisonWarningForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref<'create' | 'update' | 'view'>('create') // 表单的类型
|
||||
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
title: undefined,
|
||||
content: undefined,
|
||||
type: 1,
|
||||
level: 1,
|
||||
source: 1,
|
||||
status: 1,
|
||||
situationId: undefined,
|
||||
areaId: undefined,
|
||||
cellId: undefined,
|
||||
alertTime: undefined,
|
||||
occurTime: undefined,
|
||||
remark: undefined,
|
||||
// 展示字段
|
||||
areaName: undefined,
|
||||
cellName: undefined,
|
||||
situationTitle: undefined,
|
||||
createTime: undefined,
|
||||
// 处置流程字段
|
||||
verifyTime: undefined,
|
||||
verifier: undefined,
|
||||
verifyResult: undefined,
|
||||
handleTime: undefined,
|
||||
handler: undefined,
|
||||
handleResult: undefined,
|
||||
releaseTime: undefined,
|
||||
releaser: undefined,
|
||||
releaseReason: undefined
|
||||
})
|
||||
|
||||
const formRules = reactive({
|
||||
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
||||
level: [{ required: true, message: '请选择等级', trigger: 'change' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const areaTreeList = ref<any[]>([]) // 监区树形列表
|
||||
const cellOptions = ref<{ id: number; name: string }[]>([]) // 监室选项
|
||||
const situationOptions = ref<{ id: number; title: string }[]>([]) // 狱情选项
|
||||
|
||||
/** 获取监区树形列表 */
|
||||
const loadAreaTree = async () => {
|
||||
areaTreeList.value = await WarningApi.AreaApi.getAreaTree()
|
||||
}
|
||||
|
||||
/** 获取监室列表 */
|
||||
const loadCellList = async (areaId?: number) => {
|
||||
try {
|
||||
const data = await WarningApi.getCellList({ areaId, status: 1 })
|
||||
cellOptions.value = data
|
||||
} catch {
|
||||
cellOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取狱情列表 */
|
||||
const loadSituationList = async () => {
|
||||
try {
|
||||
const data = await WarningApi.getSituationList({ status: 1 })
|
||||
situationOptions.value = data
|
||||
} catch {
|
||||
situationOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听监区变化 */
|
||||
const handleAreaChange = (areaId: number | undefined) => {
|
||||
formData.value.cellId = undefined
|
||||
if (areaId) {
|
||||
loadCellList(areaId)
|
||||
} else {
|
||||
cellOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: 'create' | 'update' | 'view', id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = type === 'view' ? '查看详情' : t('action.' + type)
|
||||
formType.value = type
|
||||
|
||||
// 加载选项数据
|
||||
await loadAreaTree()
|
||||
await loadSituationList()
|
||||
|
||||
resetForm()
|
||||
|
||||
// 修改/查看时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await WarningApi.getWarning(id)
|
||||
formData.value = {
|
||||
id: data.id,
|
||||
title: data.title,
|
||||
content: data.content,
|
||||
type: data.type,
|
||||
level: data.level,
|
||||
source: data.source,
|
||||
status: data.status,
|
||||
situationId: data.situationId,
|
||||
areaId: data.areaId,
|
||||
cellId: data.cellId,
|
||||
alertTime: data.alertTime ? new Date(data.alertTime).getTime() : undefined,
|
||||
occurTime: data.occurTime ? new Date(data.occurTime).getTime() : undefined,
|
||||
remark: data.remark,
|
||||
areaName: data.areaName,
|
||||
cellName: data.cellName,
|
||||
situationTitle: data.situationTitle,
|
||||
createTime: data.createTime,
|
||||
verifyTime: data.verifyTime,
|
||||
verifier: data.verifier,
|
||||
verifyResult: data.verifyResult,
|
||||
handleTime: data.handleTime,
|
||||
handler: data.handler,
|
||||
handleResult: data.handleResult,
|
||||
releaseTime: data.releaseTime,
|
||||
releaser: data.releaser,
|
||||
releaseReason: data.releaseReason
|
||||
}
|
||||
// 如果有监区,加载对应的监室列表
|
||||
if (data.areaId) {
|
||||
await loadCellList(data.areaId)
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
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
|
||||
} as unknown as WarningSaveReqVO
|
||||
if (formType.value === 'create') {
|
||||
await WarningApi.createWarning(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await WarningApi.updateWarning(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
title: undefined,
|
||||
content: undefined,
|
||||
type: 1,
|
||||
level: 1,
|
||||
source: 1,
|
||||
status: 1,
|
||||
situationId: undefined,
|
||||
areaId: undefined,
|
||||
cellId: undefined,
|
||||
alertTime: undefined,
|
||||
occurTime: undefined,
|
||||
remark: undefined,
|
||||
areaName: undefined,
|
||||
cellName: undefined,
|
||||
situationTitle: undefined,
|
||||
createTime: undefined,
|
||||
verifyTime: undefined,
|
||||
verifier: undefined,
|
||||
verifyResult: undefined,
|
||||
handleTime: undefined,
|
||||
handler: undefined,
|
||||
handleResult: undefined,
|
||||
releaseTime: undefined,
|
||||
releaser: undefined,
|
||||
releaseReason: undefined
|
||||
}
|
||||
cellOptions.value = []
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// 监听监区变化
|
||||
watch(() => formData.value.areaId, (val) => {
|
||||
handleAreaChange(val)
|
||||
})
|
||||
</script>
|
||||
332
src/views/prison/warning/index.vue
Normal file
332
src/views/prison/warning/index.vue
Normal file
@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input
|
||||
v-model="queryParams.title"
|
||||
placeholder="请输入标题"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-160px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-100px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in typeOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="等级" prop="level">
|
||||
<el-select
|
||||
v-model="queryParams.level"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-100px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in levelOptions"
|
||||
: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-100px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in statusOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="来源" prop="source">
|
||||
<el-select
|
||||
v-model="queryParams.source"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-100px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in sourceOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="监区" prop="areaId">
|
||||
<el-tree-select
|
||||
v-model="queryParams.areaId"
|
||||
:data="areaTreeList"
|
||||
:props="{ label: 'name', value: 'id', children: 'children' }"
|
||||
placeholder="请选择监区"
|
||||
clearable
|
||||
filterable
|
||||
class="!w-180px"
|
||||
:render-after-expand="false"
|
||||
/>
|
||||
</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:warning:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增预警
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:warning:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="编号" align="center" prop="id" width="80" />
|
||||
<el-table-column label="标题" align="center" prop="title" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="类型" align="center" prop="type" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_WARNING_TYPE" :value="row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="等级" align="center" prop="level" width="90">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_WARNING_LEVEL" :value="row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来源" align="center" prop="source" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_WARNING_SOURCE" :value="row.source" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_WARNING_STATUS" :value="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="监区" align="center" prop="areaName" width="120" />
|
||||
<el-table-column label="预警时间" align="center" prop="alertTime" width="160">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.alertTime">{{ formatDateTime(row.alertTime) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.createTime">{{ formatDateTime(row.createTime) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('view', row.id)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> 查看
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', row.id)"
|
||||
v-hasPermi="['prison:warning:update']"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> 修改
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
v-hasPermi="['prison:warning:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> 删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<WarningForm ref="formRef" @success="getList" />
|
||||
|
||||
<!-- 预警处置操作弹窗 -->
|
||||
<WarningActionForm ref="actionFormRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { formatDateTime } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { WarningApi, WarningPageReqVO } from '@/api/prison/warning'
|
||||
import WarningForm from './WarningForm.vue'
|
||||
import WarningActionForm from './WarningActionForm.vue'
|
||||
|
||||
defineOptions({ name: 'PrisonWarning' })
|
||||
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const list = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
const selectedIds = ref<number[]>([])
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive<WarningPageReqVO>({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
title: undefined,
|
||||
type: undefined,
|
||||
level: undefined,
|
||||
status: undefined,
|
||||
source: undefined,
|
||||
areaId: undefined,
|
||||
cellId: undefined
|
||||
})
|
||||
const queryFormRef = ref()
|
||||
|
||||
// 字典数据
|
||||
const typeOptions = getIntDictOptions(DICT_TYPE.PRISON_WARNING_TYPE)
|
||||
const levelOptions = getIntDictOptions(DICT_TYPE.PRISON_WARNING_LEVEL)
|
||||
const sourceOptions = getIntDictOptions(DICT_TYPE.PRISON_WARNING_SOURCE)
|
||||
const statusOptions = getIntDictOptions(DICT_TYPE.PRISON_WARNING_STATUS)
|
||||
|
||||
// 监区选项
|
||||
const areaOptions = ref<{ id: number; name: string }[]>([])
|
||||
// 监区树形列表
|
||||
const areaTreeList = ref<any[]>([])
|
||||
|
||||
/** 获取监区树形列表 */
|
||||
const loadAreas = async () => {
|
||||
areaTreeList.value = await WarningApi.AreaApi.getAreaTree()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await WarningApi.getWarningPage(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 handleSelectionChange = (selection: any[]) => {
|
||||
selectedIds.value = selection.map((item) => item.id)
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await WarningApi.deleteWarning(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除 */
|
||||
const handleBatchDelete = async () => {
|
||||
if (selectedIds.value.length === 0) {
|
||||
message.warning('请选择要删除的数据')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await WarningApi.deleteWarningList(selectedIds.value)
|
||||
message.success(t('common.delSuccess'))
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
await message.exportConfirm()
|
||||
exportLoading.value = true
|
||||
const data = await WarningApi.exportWarning(queryParams)
|
||||
download.excel(data, '预警管理.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
loadAreas()
|
||||
})
|
||||
</script>
|
||||
Loading…
x
Reference in New Issue
Block a user