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_DEV=true
|
||||||
|
|
||||||
# 请求路径 - 本地后端服务地址
|
# 请求路径 - 后端服务地址
|
||||||
VITE_BASE_URL='http://localhost:48080'
|
VITE_BASE_URL='http://192.168.9.121:48080'
|
||||||
|
|
||||||
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
|
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
|
||||||
VITE_UPLOAD_TYPE=server
|
VITE_UPLOAD_TYPE=server
|
||||||
|
|||||||
@ -70,6 +70,10 @@ module.exports = defineConfig({
|
|||||||
'vue/no-v-html': 'off',
|
'vue/no-v-html': 'off',
|
||||||
'prettier/prettier': 'off', // 芋艿:默认关闭 prettier 的 ESLint 校验,因为我们使用的是 IDE 的 Prettier 插件
|
'prettier/prettier': 'off', // 芋艿:默认关闭 prettier 的 ESLint 校验,因为我们使用的是 IDE 的 Prettier 插件
|
||||||
'@unocss/order': 'off', // 芋艿:禁用 unocss 【css】顺序的提示,因为暂时不需要这么严格,警告也有点繁琐
|
'@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;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #f0f2f5;
|
background: linear-gradient(135deg, #1a365d 0%, #2d3748 50%, #1a365d 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-loading .app-loading-wrap {
|
.app-loading .app-loading-wrap {
|
||||||
@ -41,15 +41,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-loading .app-loading-title {
|
.app-loading .app-loading-title {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 20px;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
color: #f6e05e;
|
||||||
|
letter-spacing: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-loading .app-loading-logo {
|
.app-loading .app-loading-logo {
|
||||||
width: 100px;
|
width: 64px;
|
||||||
margin: 0 auto 15px auto;
|
height: 64px;
|
||||||
|
margin: 0 auto 10px auto;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-loading .app-loading-item {
|
.app-loading .app-loading-item {
|
||||||
@ -58,91 +62,121 @@
|
|||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
vertical-align: middle;
|
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;
|
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%;
|
height: 100%;
|
||||||
border: 4px solid #2d8cf0;
|
width: 30%;
|
||||||
border-bottom: 0;
|
background: linear-gradient(90deg, #daa520, #ffd700, #daa520);
|
||||||
border-left-color: transparent;
|
border-radius: 2px;
|
||||||
border-radius: 50%;
|
animation: progressMove 1.5s ease-in-out infinite;
|
||||||
animation: loader-outter 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-loading .app-loading-inner {
|
@keyframes progressMove {
|
||||||
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 {
|
|
||||||
0% {
|
0% {
|
||||||
-webkit-transform: rotate(0deg);
|
width: 10%;
|
||||||
transform: rotate(0deg);
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
width: 50%;
|
||||||
|
margin-left: 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
-webkit-transform: rotate(360deg);
|
width: 10%;
|
||||||
transform: rotate(360deg);
|
margin-left: 90%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes loader-outter {
|
/* 加载文字 */
|
||||||
0% {
|
.app-loading .app-loading-text {
|
||||||
-webkit-transform: rotate(0deg);
|
margin-top: 15px;
|
||||||
transform: rotate(0deg);
|
font-size: 14px;
|
||||||
}
|
color: #a0aec0;
|
||||||
|
letter-spacing: 2px;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="app-loading">
|
<div class="app-loading">
|
||||||
<div class="app-loading-wrap">
|
<div class="app-loading-wrap">
|
||||||
<div class="app-loading-title">
|
<div class="app-loading-title">%VITE_APP_TITLE%</div>
|
||||||
<img src="/logo.gif" class="app-loading-logo" alt="Logo" />
|
<!-- 书本+天平图标 -->
|
||||||
<div class="app-loading-title">%VITE_APP_TITLE%</div>
|
|
||||||
</div>
|
|
||||||
<div class="app-loading-item">
|
<div class="app-loading-item">
|
||||||
<div class="app-loading-outter"></div>
|
<svg class="app-loading-shield" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||||
<div class="app-loading-inner"></div>
|
<!-- 天平底座 -->
|
||||||
|
<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>
|
||||||
|
<div class="app-loading-progress">
|
||||||
|
<div class="app-loading-progress-bar"></div>
|
||||||
|
</div>
|
||||||
|
<div class="app-loading-text">正在加载系统中...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -109,6 +109,7 @@
|
|||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-define-config": "^2.1.0",
|
"eslint-define-config": "^2.1.0",
|
||||||
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-vue": "^9.22.0",
|
"eslint-plugin-vue": "^9.22.0",
|
||||||
"lint-staged": "^15.2.2",
|
"lint-staged": "^15.2.2",
|
||||||
|
|||||||
@ -5,6 +5,9 @@ export interface ConsumptionPageParams {
|
|||||||
pageNo: number
|
pageNo: number
|
||||||
pageSize: number
|
pageSize: number
|
||||||
prisonerNo?: string
|
prisonerNo?: string
|
||||||
|
prisonerName?: string // 罪犯姓名
|
||||||
|
prisonAreaId?: number // 监区ID
|
||||||
|
prisonCellId?: number // 监室ID
|
||||||
type?: number
|
type?: number
|
||||||
status?: number
|
status?: number
|
||||||
totalAmount?: number
|
totalAmount?: number
|
||||||
@ -26,6 +29,11 @@ export interface Consumption {
|
|||||||
id: number // 订单ID
|
id: number // 订单ID
|
||||||
prisonerId?: number // 罪犯ID
|
prisonerId?: number // 罪犯ID
|
||||||
prisonerNo?: string // 罪犯编号
|
prisonerNo?: string // 罪犯编号
|
||||||
|
prisonerName?: string // 罪犯姓名
|
||||||
|
prisonAreaId?: number // 监区ID
|
||||||
|
prisonAreaName?: string // 监区名称
|
||||||
|
prisonCellId?: number // 监室ID
|
||||||
|
prisonCellName?: string // 监室名称
|
||||||
orderNo?: string // 订单号
|
orderNo?: string // 订单号
|
||||||
type?: number // 类型:1-购物 2-餐饮 3-医疗 4-通讯 5-其他
|
type?: number // 类型:1-购物 2-餐饮 3-医疗 4-通讯 5-其他
|
||||||
totalAmount?: number // 订单总金额
|
totalAmount?: number // 订单总金额
|
||||||
@ -34,6 +42,7 @@ export interface Consumption {
|
|||||||
status?: number // 状态:1-成功 2-失败
|
status?: number // 状态:1-成功 2-失败
|
||||||
remark: string // 备注
|
remark: string // 备注
|
||||||
details?: ConsumptionDetail[] // 消费明细列表
|
details?: ConsumptionDetail[] // 消费明细列表
|
||||||
|
createTime?: Date // 创建时间
|
||||||
}
|
}
|
||||||
|
|
||||||
// 消费订单 API
|
// 消费订单 API
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export interface ChartDataVO {
|
|||||||
/** 省份数据 */
|
/** 省份数据 */
|
||||||
export interface ProvinceChartVO {
|
export interface ProvinceChartVO {
|
||||||
province: string // 省份名称
|
province: string // 省份名称
|
||||||
provinceCode: number // 省份编码
|
provinceCode: string | number // 省份编码(支持字符串名称或数字编码)
|
||||||
count: number // 人数
|
count: number // 人数
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,19 @@
|
|||||||
import request from '@/config/axios'
|
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 {
|
export interface PrisonerVO {
|
||||||
id: number
|
id: number
|
||||||
prisonerNo: string
|
prisonerNo: string
|
||||||
name: string
|
name: string
|
||||||
gender: number
|
gender: number // 性别:0-未知 1-男 2-女
|
||||||
genderName?: string // 性别名称
|
genderName?: string // 性别名称
|
||||||
birthday: string
|
birthday: string
|
||||||
idCard: string
|
idCard: string
|
||||||
@ -47,7 +56,7 @@ export interface PrisonerCreateVO {
|
|||||||
id?: number
|
id?: number
|
||||||
prisonerNo: string
|
prisonerNo: string
|
||||||
name: string
|
name: string
|
||||||
gender: number
|
gender: number // 性别:0-未知 1-男 2-女
|
||||||
birthday: string
|
birthday: string
|
||||||
idCard: string
|
idCard: string
|
||||||
ethnicity: string
|
ethnicity: string
|
||||||
@ -74,39 +83,48 @@ export interface PrisonerCreateVO {
|
|||||||
remark: string
|
remark: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 服刑人员分页查询
|
// PrisonerApi 对象 - 统一使用对象导出模式
|
||||||
export const getPrisonerPage = (params: PageParam) => {
|
export const PrisonerApi = {
|
||||||
return request.get({ url: '/prison/prisoner/page', params })
|
// 分页查询
|
||||||
}
|
getPage: (params: PageParam) => {
|
||||||
|
return request.get({ url: '/prison/prisoner/page', params })
|
||||||
// 服刑人员详情
|
},
|
||||||
export const getPrisoner = (id: number) => {
|
// 获取详情
|
||||||
return request.get({ url: '/prison/prisoner/get?id=' + id })
|
get: (id: number) => {
|
||||||
}
|
return request.get({ url: '/prison/prisoner/get', params: { id } })
|
||||||
|
},
|
||||||
// 新增服刑人员
|
// 创建
|
||||||
export const createPrisoner = (data: PrisonerCreateVO) => {
|
create: (data: PrisonerCreateVO) => {
|
||||||
return request.post({ url: '/prison/prisoner/create', data })
|
return request.post({ url: '/prison/prisoner/create', data })
|
||||||
}
|
},
|
||||||
|
// 更新
|
||||||
// 修改服刑人员
|
update: (data: PrisonerCreateVO) => {
|
||||||
export const updatePrisoner = (data: PrisonerCreateVO) => {
|
return request.put({ url: '/prison/prisoner/update', data })
|
||||||
return request.put({ url: '/prison/prisoner/update', data })
|
},
|
||||||
}
|
// 删除
|
||||||
|
delete: (id: number) => {
|
||||||
// 删除服刑人员
|
return request.delete({ url: '/prison/prisoner/delete', params: { id } })
|
||||||
export const deletePrisoner = (id: number) => {
|
},
|
||||||
return request.delete({ url: '/prison/prisoner/delete?id=' + id })
|
// 批量删除
|
||||||
}
|
deleteList: (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/prison/prisoner/delete-list', params: { ids: ids.join(',') } })
|
||||||
// 批量删除服刑人员
|
},
|
||||||
export const deletePrisonerList = (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 })
|
||||||
|
},
|
||||||
// 导出服刑人员
|
// 调监
|
||||||
export const exportPrisoner = (params) => {
|
doTransfer: (data: TransferReqVO) => {
|
||||||
return request.download({ url: '/prison/prisoner/export-excel', params })
|
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
|
reason?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行调监
|
|
||||||
export const doTransfer = (data: TransferReqVO) => {
|
|
||||||
return request.post({ url: '/prison/prisoner/transfer', params: data })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 罪犯位置历史记录
|
// 罪犯位置历史记录
|
||||||
export interface PrisonerAreaLogVO {
|
export interface PrisonerAreaLogVO {
|
||||||
id: number
|
id: number
|
||||||
@ -142,27 +155,3 @@ export interface PrisonerAreaLogVO {
|
|||||||
operatorName: string
|
operatorName: string
|
||||||
createTime: 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 {
|
export interface Question {
|
||||||
id?: number // 问题ID(创建时不需要)
|
id?: number // 问题ID(创建时不需要)
|
||||||
questionnaireId?: number // 所属问卷ID
|
questionnaireId: number // 所属问卷ID
|
||||||
title?: string // 问题标题
|
title: string // 问题标题
|
||||||
type?: number // 问题类型:1-单选 2-多选 3-填空 4-评分 5-日期 6-数字
|
type: number // 问题类型:1-单选 2-多选 3-填空 4-评分 5-日期 6-数字
|
||||||
options?: string // 选项JSON
|
options?: string // 选项JSON(单选/多选时使用)
|
||||||
score?: number // 分值
|
score?: number // 分值
|
||||||
sort?: number // 排序
|
sort?: number // 排序
|
||||||
isRequired?: boolean // 是否必答
|
isRequired?: boolean // 是否必答
|
||||||
// 新增字段
|
|
||||||
partName?: string // 分区名称
|
partName?: string // 分区名称
|
||||||
partSort?: number // 分区排序
|
partSort?: number // 分区排序
|
||||||
helpText?: string // 帮助说明
|
helpText?: string // 帮助说明文字
|
||||||
placeholder?: string // 占位提示
|
placeholder?: string // 占位提示
|
||||||
defaultValue?: string // 默认值
|
defaultValue?: string // 默认值
|
||||||
autoFillType?: string // 自动填充类型:NONE/AUTO/MANUAL
|
autoFillType?: string // 自动填充类型:NONE-无 AUTO-自动填充 MANUAL-手动输入
|
||||||
autoFillSource?: string // 自动填充来源
|
autoFillSource?: string // 自动填充来源
|
||||||
displayCondition?: string // 显示条件JSON
|
displayCondition?: string // 显示条件JSON
|
||||||
minValue?: number // 最小值
|
minValue?: number // 最小值
|
||||||
@ -28,23 +27,20 @@ export interface Question {
|
|||||||
export interface QuestionPageParams {
|
export interface QuestionPageParams {
|
||||||
pageNo: number
|
pageNo: number
|
||||||
pageSize: number
|
pageSize: number
|
||||||
questionnaireId?: number
|
questionnaireId?: number // 所属问卷ID
|
||||||
title?: string
|
title?: string // 问题标题
|
||||||
type?: number
|
type?: number // 问题类型
|
||||||
partName?: string
|
partName?: string // 分区名称
|
||||||
}
|
|
||||||
|
|
||||||
/** 批量更新参数 */
|
|
||||||
export interface BatchUpdateQuestion {
|
|
||||||
id: number
|
|
||||||
partName?: string
|
|
||||||
partSort?: number
|
|
||||||
sort?: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 批量更新请求 */
|
/** 批量更新请求 */
|
||||||
export interface BatchUpdateReq {
|
export interface QuestionBatchUpdateReq {
|
||||||
questions: BatchUpdateQuestion[]
|
questions: Array<{
|
||||||
|
id: number
|
||||||
|
partName?: string
|
||||||
|
partSort?: number
|
||||||
|
sort?: number
|
||||||
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
// 问卷问题 API
|
// 问卷问题 API
|
||||||
@ -79,13 +75,13 @@ export const QuestionApi = {
|
|||||||
return await request.delete<boolean>({ url: `/prison/question/delete-list`, params: { ids: ids.join(',') } })
|
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
|
// 导出问卷问题 Excel
|
||||||
exportQuestion: async (params: QuestionPageParams) => {
|
exportQuestion: async (params: QuestionPageParams) => {
|
||||||
return await request.download({ url: `/prison/question/export-excel`, params })
|
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
|
title?: string
|
||||||
type?: number
|
type?: number
|
||||||
status?: number
|
status?: number
|
||||||
|
description?: string
|
||||||
coverImage?: string
|
coverImage?: string
|
||||||
instruction?: string
|
instruction?: string
|
||||||
estimatedTime?: number
|
estimatedTime?: number
|
||||||
partCount?: number
|
partCount?: number
|
||||||
allowAnonymous?: boolean
|
allowAnonymous?: boolean
|
||||||
|
createTime?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 问卷模板 API
|
// 问卷模板 API
|
||||||
export const QuestionnaireApi = {
|
export const QuestionnaireApi = {
|
||||||
// 查询问卷模板分页
|
// 查询问卷模板分页
|
||||||
getQuestionnairePage: async (params: QuestionnairePageParams) => {
|
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 // 总分
|
totalScore?: number // 总分
|
||||||
passScore?: number // 及格分数
|
passScore?: number // 及格分数
|
||||||
passStatus?: number // 及格状态:1-及格 2-不及格 3-待评阅
|
passStatus?: number // 及格状态:1-及格 2-不及格 3-待评阅
|
||||||
riskLevel?: number // 风险等级:1-高风险 2-中风险 3-低风险
|
riskLevel?: number // 风险等级:1-低风险 2-中风险 3-高风险 4-极高风险
|
||||||
evaluatorId?: number // 评阅人ID
|
evaluatorId?: number // 评阅人ID
|
||||||
evaluatorName?: string // 评阅人姓名
|
evaluatorName?: string // 评阅人姓名
|
||||||
evaluateTime?: string // 评阅时间
|
evaluateTime?: string // 评阅时间
|
||||||
@ -73,7 +73,7 @@ export interface AssessmentManualScoreReq {
|
|||||||
recordId: number // 测评记录ID
|
recordId: number // 测评记录ID
|
||||||
subjectiveScore: number // 主观题得分
|
subjectiveScore: number // 主观题得分
|
||||||
comment?: string // 评语
|
comment?: string // 评语
|
||||||
riskLevel?: number // 风险等级:1-高风险 2-中风险 3-低风险
|
riskLevel?: number // 风险等级:1-低风险 2-中风险 3-高风险 4-极高风险
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 分数分布数据 */
|
/** 分数分布数据 */
|
||||||
@ -87,6 +87,7 @@ export interface ScoreDistribution {
|
|||||||
|
|
||||||
/** 风险分布数据 */
|
/** 风险分布数据 */
|
||||||
export interface RiskDistribution {
|
export interface RiskDistribution {
|
||||||
|
extreme?: number // 极高风险
|
||||||
high?: number
|
high?: number
|
||||||
medium?: number
|
medium?: number
|
||||||
low?: 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 {
|
export interface RiskAssessmentPageParams {
|
||||||
pageNo: number
|
pageNo: number
|
||||||
pageSize: number
|
pageSize: number
|
||||||
|
prisonerId?: number
|
||||||
prisonerNo?: string
|
prisonerNo?: string
|
||||||
|
prisonerName?: string
|
||||||
assessmentType?: number
|
assessmentType?: number
|
||||||
riskLevel?: number
|
riskLevel?: number
|
||||||
status?: number
|
status?: number
|
||||||
@ -15,6 +17,7 @@ export interface RiskAssessment {
|
|||||||
id: number // 评估ID
|
id: number // 评估ID
|
||||||
prisonerId?: number // 罪犯ID
|
prisonerId?: number // 罪犯ID
|
||||||
prisonerNo?: string // 罪犯编号
|
prisonerNo?: string // 罪犯编号
|
||||||
|
prisonerName?: string // 罪犯姓名
|
||||||
assessmentType?: number // 评估类型:1-入狱评估 2-定期评估 3-专项评估
|
assessmentType?: number // 评估类型:1-入狱评估 2-定期评估 3-专项评估
|
||||||
assessmentDate?: string // 评估日期
|
assessmentDate?: string // 评估日期
|
||||||
violenceScore: number // 暴力倾向得分
|
violenceScore: number // 暴力倾向得分
|
||||||
|
|||||||
@ -5,6 +5,9 @@ export interface ScorePageParams {
|
|||||||
pageNo: number
|
pageNo: number
|
||||||
pageSize: number
|
pageSize: number
|
||||||
prisonerNo?: string
|
prisonerNo?: string
|
||||||
|
prisonerName?: string // 罪犯姓名
|
||||||
|
prisonAreaId?: number // 监区ID
|
||||||
|
prisonCellId?: number // 监室ID
|
||||||
year?: number
|
year?: number
|
||||||
month?: number
|
month?: number
|
||||||
level?: number
|
level?: number
|
||||||
@ -16,6 +19,11 @@ export interface Score {
|
|||||||
id: number // 记录ID
|
id: number // 记录ID
|
||||||
prisonerId?: number // 罪犯ID
|
prisonerId?: number // 罪犯ID
|
||||||
prisonerNo?: string // 罪犯编号
|
prisonerNo?: string // 罪犯编号
|
||||||
|
prisonerName?: string // 罪犯姓名
|
||||||
|
prisonAreaId?: number // 监区ID
|
||||||
|
prisonAreaName?: string // 监区名称
|
||||||
|
prisonCellId?: number // 监室ID
|
||||||
|
prisonCellName?: string // 监室名称
|
||||||
year?: number // 考核年份
|
year?: number // 考核年份
|
||||||
month?: number // 考核月份
|
month?: number // 考核月份
|
||||||
baseScore: number // 基础分
|
baseScore: number // 基础分
|
||||||
@ -27,6 +35,8 @@ export interface Score {
|
|||||||
assessorName: string // 考核人姓名
|
assessorName: string // 考核人姓名
|
||||||
status?: number // 状态:1-待审核 2-已通过 3-已驳回
|
status?: number // 状态:1-待审核 2-已通过 3-已驳回
|
||||||
remark: string // 备注
|
remark: string // 备注
|
||||||
|
createTime?: Date // 创建时间
|
||||||
|
updateTime?: Date // 更新时间
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计分考核 API
|
// 计分考核 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 type { App } from 'vue'
|
||||||
import { Icon } from './Icon'
|
import { Icon } from './Icon'
|
||||||
|
import JusticeIcon from './Icon/JusticeIcon.vue'
|
||||||
|
|
||||||
export const setupGlobCom = (app: App<Element>): void => {
|
export const setupGlobCom = (app: App<Element>): void => {
|
||||||
app.component('Icon', Icon)
|
app.component('Icon', Icon)
|
||||||
|
app.component('JusticeIcon', JusticeIcon)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -768,6 +768,32 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
canTo: 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'
|
import { ElementPlusInfoType } from '@/types/elementPlus'
|
||||||
|
|
||||||
const dictStore = useDictStoreWithOut()
|
const dictStore = _useDictStoreWithOut()
|
||||||
|
|
||||||
|
// Re-export for convenience
|
||||||
|
export const useDictStoreWithOut = _useDictStoreWithOut
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 dictType 对应的数据字典数组
|
* 获取 dictType 对应的数据字典数组
|
||||||
@ -276,5 +279,10 @@ export enum DICT_TYPE {
|
|||||||
PRISON_CERTIFICATE_TYPE = 'prison_certificate_type', // 证件类型:1-身份证 2-户口簿 3-其他
|
PRISON_CERTIFICATE_TYPE = 'prison_certificate_type', // 证件类型:1-身份证 2-户口簿 3-其他
|
||||||
PRISON_ASSESSMENT_STATUS = 'prison_assessment_status', // 测评状态:1-待测评 2-测评中 3-已完成 4-已过期
|
PRISON_ASSESSMENT_STATUS = 'prison_assessment_status', // 测评状态:1-待测评 2-测评中 3-已完成 4-已过期
|
||||||
PRISON_ASSESSMENT_PASS_STATUS = 'prison_assessment_pass_status', // 测评及格状态:1-及格 2-不及格 3-待评分
|
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') : ''
|
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 字段值
|
* @param cellValue 字段值
|
||||||
*/
|
*/
|
||||||
export function dateFormatter(_row: any, _column: TableColumnCtx<any>, cellValue: any): string {
|
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>
|
<template>
|
||||||
|
<!-- 自定义监狱风格加载动画 -->
|
||||||
|
<Transition name="fade">
|
||||||
|
<PrisonLoginLoading
|
||||||
|
v-if="customLoadingVisible"
|
||||||
|
:text="loadingText"
|
||||||
|
:background="loadingBackground"
|
||||||
|
:lock="loadingLock"
|
||||||
|
:system-name="loadingSystemName"
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
<el-form
|
<el-form
|
||||||
v-show="getShow"
|
v-show="getShow"
|
||||||
ref="formLogin"
|
ref="formLogin"
|
||||||
@ -150,6 +160,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ElLoading } from 'element-plus'
|
import { ElLoading } from 'element-plus'
|
||||||
import LoginFormTitle from './LoginFormTitle.vue'
|
import LoginFormTitle from './LoginFormTitle.vue'
|
||||||
|
import PrisonLoginLoading from './PrisonLoginLoading.vue'
|
||||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||||
|
|
||||||
import { useIcon } from '@/hooks/web/useIcon'
|
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) => {
|
const handleLogin = async (params: any) => {
|
||||||
loginLoading.value = true
|
loginLoading.value = true
|
||||||
@ -261,11 +279,8 @@ const handleLogin = async (params: any) => {
|
|||||||
if (!res) {
|
if (!res) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loading.value = ElLoading.service({
|
// 使用自定义监狱风格加载动画
|
||||||
lock: true,
|
customLoadingVisible.value = true
|
||||||
text: '正在加载系统中...',
|
|
||||||
background: 'rgba(0, 0, 0, 0.7)'
|
|
||||||
})
|
|
||||||
if (loginDataLoginForm.rememberMe) {
|
if (loginDataLoginForm.rememberMe) {
|
||||||
authUtil.setLoginForm(loginDataLoginForm)
|
authUtil.setLoginForm(loginDataLoginForm)
|
||||||
} else {
|
} else {
|
||||||
@ -283,7 +298,7 @@ const handleLogin = async (params: any) => {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loginLoading.value = false
|
loginLoading.value = false
|
||||||
loading.value.close()
|
customLoadingVisible.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,4 +372,15 @@ onMounted(() => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 自定义加载动画过渡效果
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
</style>
|
</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>
|
||||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ dateFormatter(scope.row.createTime) }}
|
{{ formatDateTime(scope.row.createTime) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center" width="120">
|
<el-table-column label="操作" align="center" width="120" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -138,7 +138,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { formatDateTime } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { CellApi } from '@/api/prison/cell'
|
import { CellApi } from '@/api/prison/cell'
|
||||||
import { AreaApi } from '@/api/prison/area'
|
import { AreaApi } from '@/api/prison/area'
|
||||||
|
|||||||
@ -77,7 +77,7 @@
|
|||||||
<el-divider content-position="left">消费明细</el-divider>
|
<el-divider content-position="left">消费明细</el-divider>
|
||||||
<el-table :data="formData.details" border>
|
<el-table :data="formData.details" border>
|
||||||
<el-table-column label="商品名称" prop="goodsName" width="150">
|
<el-table-column label="商品名称" prop="goodsName" width="150">
|
||||||
<template #default="{ row, $index }">
|
<template #default="{ row }">
|
||||||
<el-input v-model="row.goodsName" placeholder="商品名称" />
|
<el-input v-model="row.goodsName" placeholder="商品名称" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -102,7 +102,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="80">
|
<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>
|
<el-button type="danger" link @click="removeDetail($index)" :disabled="formData.details.length <= 1">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|||||||
@ -17,6 +17,15 @@
|
|||||||
class="!w-140px"
|
class="!w-140px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</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-form-item label="类型" prop="type">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="queryParams.type"
|
v-model="queryParams.type"
|
||||||
@ -89,8 +98,11 @@
|
|||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" />
|
<el-table-column type="selection" width="55" />
|
||||||
<el-table-column label="订单ID" align="center" prop="id" width="80" />
|
<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="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">
|
<el-table-column label="类型" align="center" prop="type" width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<dict-tag :type="DICT_TYPE.PRISON_CONSUMPTION_TYPE" :value="scope.row.type" />
|
<dict-tag :type="DICT_TYPE.PRISON_CONSUMPTION_TYPE" :value="scope.row.type" />
|
||||||
@ -113,7 +125,7 @@
|
|||||||
{{ formatDateTime(scope.row.createTime) }}
|
{{ formatDateTime(scope.row.createTime) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center" width="150">
|
<el-table-column label="操作" align="center" width="150" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -176,6 +188,7 @@ const queryParams = reactive({
|
|||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
prisonerNo: undefined,
|
prisonerNo: undefined,
|
||||||
|
prisonerName: undefined,
|
||||||
type: undefined,
|
type: undefined,
|
||||||
status: undefined
|
status: undefined
|
||||||
})
|
})
|
||||||
|
|||||||
@ -75,7 +75,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import type { EChartsOption } from 'echarts'
|
import type { EChartsOption } from 'echarts'
|
||||||
import { StatCard } from './components'
|
import StatCard from './components/StatCard.vue'
|
||||||
import { DashboardApi, type DashboardStatisticsVO } from '@/api/prison/dashboard'
|
import { DashboardApi, type DashboardStatisticsVO } from '@/api/prison/dashboard'
|
||||||
import EChart from '@/components/Echart/src/Echart.vue'
|
import EChart from '@/components/Echart/src/Echart.vue'
|
||||||
import ChinaMap from './components/ChinaMap.vue'
|
import ChinaMap from './components/ChinaMap.vue'
|
||||||
@ -129,6 +129,7 @@ const ageChartOptions = computed<EChartsOption>(() => ({
|
|||||||
top: 'center',
|
top: 'center',
|
||||||
icon: 'circle'
|
icon: 'circle'
|
||||||
},
|
},
|
||||||
|
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272'],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
@ -170,6 +171,7 @@ const sentenceChartOptions = computed<EChartsOption>(() => ({
|
|||||||
top: 'center',
|
top: 'center',
|
||||||
icon: 'circle'
|
icon: 'circle'
|
||||||
},
|
},
|
||||||
|
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de'],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
@ -211,6 +213,7 @@ const educationChartOptions = computed<EChartsOption>(() => ({
|
|||||||
top: 'center',
|
top: 'center',
|
||||||
icon: 'circle'
|
icon: 'circle'
|
||||||
},
|
},
|
||||||
|
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4'],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
|
|||||||
@ -6,14 +6,16 @@
|
|||||||
<el-descriptions-item label="罪犯编号">{{ data.prisonerNo }}</el-descriptions-item>
|
<el-descriptions-item label="罪犯编号">{{ data.prisonerNo }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="姓名">{{ data.name }}</el-descriptions-item>
|
<el-descriptions-item label="姓名">{{ data.name }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="性别">
|
<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>
|
||||||
<el-descriptions-item label="身份证号">{{ data.idCard }}</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.birthday }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="民族">{{ data.ethnicity }}</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="籍贯" :span="2">{{ data.nativePlace }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="文化程度">
|
<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>
|
||||||
<el-descriptions-item label="职业">{{ data.occupation }}</el-descriptions-item>
|
<el-descriptions-item label="职业">{{ data.occupation }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="家庭住址" :span="2">{{ data.address }}</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 title="监管信息" :column="2" border style="margin-top: 20px">
|
||||||
<el-descriptions-item label="监管等级">
|
<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>
|
||||||
<el-descriptions-item label="风险等级">
|
<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>
|
||||||
<el-descriptions-item label="当前监区">{{ data.prisonAreaName }}</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="当前监室">{{ data.prisonCellName }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="状态">
|
<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>
|
||||||
<el-descriptions-item label="备注">{{ data.remark }}</el-descriptions-item>
|
<el-descriptions-item label="备注">{{ data.remark }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
@ -55,7 +60,7 @@
|
|||||||
<el-timeline-item
|
<el-timeline-item
|
||||||
v-for="(log, index) in areaHistory"
|
v-for="(log, index) in areaHistory"
|
||||||
:key="index"
|
:key="index"
|
||||||
:timestamp="log.createTime"
|
:timestamp="formatDateTime(log.createTime)"
|
||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<el-card>
|
<el-card>
|
||||||
@ -74,7 +79,8 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE } from '@/utils/dict'
|
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' })
|
defineOptions({ name: 'PrisonPrisonerDetail' })
|
||||||
|
|
||||||
@ -89,9 +95,9 @@ const open = async (id: number) => {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
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 {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -273,7 +273,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { PrisonerCreateVO } from '@/api/prison/prisoner'
|
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 { AreaApi } from '@/api/prison/area'
|
||||||
import { CellApi } from '@/api/prison/cell'
|
import { CellApi } from '@/api/prison/cell'
|
||||||
|
|
||||||
@ -447,7 +447,7 @@ const resetForm = () => {
|
|||||||
const getPrisonerDetail = async (id: number) => {
|
const getPrisonerDetail = async (id: number) => {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
const data = await PrisonerApi.getPrisoner(id)
|
const data = await PrisonerApi.get(id)
|
||||||
formData.value = {
|
formData.value = {
|
||||||
...formData.value,
|
...formData.value,
|
||||||
...data
|
...data
|
||||||
@ -468,10 +468,10 @@ const submitForm = async () => {
|
|||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
if (dialogType.value === 'create') {
|
if (dialogType.value === 'create') {
|
||||||
await PrisonerApi.createPrisoner(formData.value)
|
await PrisonerApi.create(formData.value)
|
||||||
message.success('入监登记成功')
|
message.success('入监登记成功')
|
||||||
} else {
|
} else {
|
||||||
await PrisonerApi.updatePrisoner(formData.value)
|
await PrisonerApi.update(formData.value)
|
||||||
message.success('修改成功')
|
message.success('修改成功')
|
||||||
}
|
}
|
||||||
dialogVisible.value = false
|
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>
|
<script lang="ts" setup>
|
||||||
import { ElMessage } from 'element-plus'
|
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 { AreaApi } from '@/api/prison/area'
|
||||||
import { CellApi } from '@/api/prison/cell'
|
import { CellApi } from '@/api/prison/cell'
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ const open = async (prisonerId: number) => {
|
|||||||
resetForm()
|
resetForm()
|
||||||
try {
|
try {
|
||||||
// 获取罪犯信息
|
// 获取罪犯信息
|
||||||
prisoner.value = await PrisonerApi.getPrisoner(prisonerId)
|
prisoner.value = await PrisonerApi.get(prisonerId)
|
||||||
formData.prisonerId = prisonerId
|
formData.prisonerId = prisonerId
|
||||||
// 加载监区列表
|
// 加载监区列表
|
||||||
const treeData = await AreaApi.getAreaTree()
|
const treeData = await AreaApi.getAreaTree()
|
||||||
|
|||||||
@ -205,8 +205,16 @@
|
|||||||
<dict-tag :type="DICT_TYPE.PRISONER_STATUS" :value="scope.row.status" />
|
<dict-tag :type="DICT_TYPE.PRISONER_STATUS" :value="scope.row.status" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center" width="240">
|
<el-table-column label="操作" align="center" width="300" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
@click="handleWorkbench(scope.row)"
|
||||||
|
v-hasPermi="['prison:prisoner:query']"
|
||||||
|
>
|
||||||
|
工作台
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
@ -257,20 +265,24 @@
|
|||||||
<!-- 详情弹窗 -->
|
<!-- 详情弹窗 -->
|
||||||
<PrisonerDetail ref="detailRef" />
|
<PrisonerDetail ref="detailRef" />
|
||||||
|
|
||||||
|
<!-- 工作台弹窗 -->
|
||||||
|
<PrisonerWorkbench ref="workbenchRef" />
|
||||||
|
|
||||||
<!-- 调监弹窗 -->
|
<!-- 调监弹窗 -->
|
||||||
<TransferForm ref="transferRef" @success="getList" />
|
<TransferForm ref="transferRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { checkPermi } from '@/utils/permission'
|
|
||||||
import PrisonerForm from './PrisonerForm.vue'
|
import PrisonerForm from './PrisonerForm.vue'
|
||||||
import PrisonerDetail from './PrisonerDetail.vue'
|
import PrisonerDetail from './PrisonerDetail.vue'
|
||||||
|
import PrisonerWorkbench from './PrisonerWorkbench.vue'
|
||||||
import TransferForm from './TransferForm.vue'
|
import TransferForm from './TransferForm.vue'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import * as PrisonerApi from '@/api/prison/prisoner'
|
import { PrisonerApi } from '@/api/prison/prisoner'
|
||||||
import { AreaApi } from '@/api/prison/area'
|
import type { PrisonerVO, PrisonerCreateVO } from '@/api/prison/prisoner'
|
||||||
import { CellApi } from '@/api/prison/cell'
|
import { AreaApi, type AreaNode } from '@/api/prison/area'
|
||||||
|
import { CellApi, type CellVO } from '@/api/prison/cell'
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
|
||||||
defineOptions({ name: 'PrisonPrisoner' })
|
defineOptions({ name: 'PrisonPrisoner' })
|
||||||
@ -280,9 +292,9 @@ const message = useMessage() // 消息弹窗
|
|||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
const list = ref([]) // 列表的数据
|
const list = ref<PrisonerVO[]>([]) // 列表的数据
|
||||||
const areaTreeList = ref([]) // 监区树形列表
|
const areaTreeList = ref<AreaNode[]>([]) // 监区树形列表
|
||||||
const cellList = ref([]) // 监室列表
|
const cellList = ref<CellVO[]>([]) // 监室列表
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@ -303,6 +315,7 @@ const queryFormRef = ref() // 搜索的表单
|
|||||||
const exportLoading = ref(false) // 导出的加载中
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
const formRef = ref() // 表单
|
const formRef = ref() // 表单
|
||||||
const detailRef = ref() // 详情
|
const detailRef = ref() // 详情
|
||||||
|
const workbenchRef = ref() // 工作台
|
||||||
const transferRef = ref() // 调监表单
|
const transferRef = ref() // 调监表单
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
@ -317,7 +330,7 @@ const getList = async () => {
|
|||||||
queryParams.imprisonmentDateStart = undefined
|
queryParams.imprisonmentDateStart = undefined
|
||||||
queryParams.imprisonmentDateEnd = undefined
|
queryParams.imprisonmentDateEnd = undefined
|
||||||
}
|
}
|
||||||
const data = await PrisonerApi.getPrisonerPage(queryParams)
|
const data = await PrisonerApi.getPage(queryParams)
|
||||||
list.value = data.list
|
list.value = data.list
|
||||||
total.value = data.total
|
total.value = data.total
|
||||||
} finally {
|
} finally {
|
||||||
@ -348,7 +361,7 @@ const handleExport = async () => {
|
|||||||
await message.exportConfirm()
|
await message.exportConfirm()
|
||||||
// 发起导出
|
// 发起导出
|
||||||
exportLoading.value = true
|
exportLoading.value = true
|
||||||
const data = await PrisonerApi.exportPrisoner(queryParams)
|
const data = await PrisonerApi.export(queryParams)
|
||||||
download.excel(data, '罪犯信息.xls')
|
download.excel(data, '罪犯信息.xls')
|
||||||
} catch {
|
} catch {
|
||||||
} finally {
|
} finally {
|
||||||
@ -362,10 +375,15 @@ const openForm = (type: string, id?: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 查看详情 */
|
/** 查看详情 */
|
||||||
const handleDetail = (row: any) => {
|
const handleDetail = (row: PrisonerVO) => {
|
||||||
detailRef.value.open(row.id)
|
detailRef.value.open(row.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 打开工作台 */
|
||||||
|
const handleWorkbench = (row: PrisonerVO) => {
|
||||||
|
workbenchRef.value.open(row.id)
|
||||||
|
}
|
||||||
|
|
||||||
/** 打开调监弹窗 */
|
/** 打开调监弹窗 */
|
||||||
const openTransferForm = () => {
|
const openTransferForm = () => {
|
||||||
if (checkedIds.value.length !== 1) {
|
if (checkedIds.value.length !== 1) {
|
||||||
@ -376,7 +394,7 @@ const openTransferForm = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 调监操作 */
|
/** 调监操作 */
|
||||||
const handleTransfer = (row: any) => {
|
const handleTransfer = (row: PrisonerVO) => {
|
||||||
transferRef.value.open(row.id)
|
transferRef.value.open(row.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,7 +403,7 @@ const handleAreaChange = async (areaId: number) => {
|
|||||||
queryParams.prisonCellId = undefined
|
queryParams.prisonCellId = undefined
|
||||||
cellList.value = []
|
cellList.value = []
|
||||||
if (areaId) {
|
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) {
|
if (row.lifeImprisonment === 1) {
|
||||||
return '无期'
|
return '无期'
|
||||||
}
|
}
|
||||||
@ -422,7 +440,7 @@ const handleDelete = async (id: number) => {
|
|||||||
// 删除的二次确认
|
// 删除的二次确认
|
||||||
await message.delConfirm()
|
await message.delConfirm()
|
||||||
// 发起删除
|
// 发起删除
|
||||||
await PrisonerApi.deletePrisoner(id)
|
await PrisonerApi.delete(id)
|
||||||
message.success(t('common.delSuccess'))
|
message.success(t('common.delSuccess'))
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
await getList()
|
await getList()
|
||||||
@ -431,7 +449,7 @@ const handleDelete = async (id: number) => {
|
|||||||
|
|
||||||
/** 批量删除按钮操作 */
|
/** 批量删除按钮操作 */
|
||||||
const checkedIds = ref<number[]>([])
|
const checkedIds = ref<number[]>([])
|
||||||
const handleRowCheckboxChange = (rows: any[]) => {
|
const handleRowCheckboxChange = (rows: PrisonerVO[]) => {
|
||||||
checkedIds.value = rows.map((row) => row.id)
|
checkedIds.value = rows.map((row) => row.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -440,7 +458,7 @@ const handleDeleteBatch = async () => {
|
|||||||
// 删除的二次确认
|
// 删除的二次确认
|
||||||
await message.delConfirm()
|
await message.delConfirm()
|
||||||
// 发起批量删除
|
// 发起批量删除
|
||||||
await PrisonerApi.deletePrisonerList(checkedIds.value)
|
await PrisonerApi.deleteList(checkedIds.value)
|
||||||
checkedIds.value = []
|
checkedIds.value = []
|
||||||
message.success(t('common.delSuccess'))
|
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">
|
<el-form-item label="问卷说明" prop="description">
|
||||||
<Editor v-model="formData.description" height="150px" />
|
<Editor v-model="formData.description" height="150px" />
|
||||||
</el-form-item>
|
</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-form-item label="总分" prop="totalScore">
|
||||||
<el-input v-model="formData.totalScore" placeholder="请输入总分" />
|
<el-input v-model="formData.totalScore" placeholder="请输入总分" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="及格分" prop="passScore">
|
<el-form-item label="及格分" prop="passScore">
|
||||||
<el-input v-model="formData.passScore" placeholder="请输入及格分" />
|
<el-input v-model="formData.passScore" placeholder="请输入及格分" />
|
||||||
</el-form-item>
|
</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-form-item label="状态" prop="status">
|
||||||
<el-select v-model="formData.status" placeholder="请选择状态">
|
<el-select v-model="formData.status" placeholder="请选择状态">
|
||||||
<el-option
|
<el-option
|
||||||
@ -65,8 +79,12 @@ const formData = ref({
|
|||||||
title: undefined,
|
title: undefined,
|
||||||
type: undefined,
|
type: undefined,
|
||||||
description: undefined,
|
description: undefined,
|
||||||
|
coverImage: [] as string[],
|
||||||
|
instruction: undefined,
|
||||||
|
estimatedTime: undefined,
|
||||||
totalScore: undefined,
|
totalScore: undefined,
|
||||||
passScore: undefined,
|
passScore: undefined,
|
||||||
|
allowAnonymous: false,
|
||||||
status: undefined
|
status: undefined
|
||||||
})
|
})
|
||||||
const formRules = reactive({
|
const formRules = reactive({
|
||||||
@ -125,8 +143,12 @@ const resetForm = () => {
|
|||||||
title: undefined,
|
title: undefined,
|
||||||
type: undefined,
|
type: undefined,
|
||||||
description: undefined,
|
description: undefined,
|
||||||
|
coverImage: [] as string[],
|
||||||
|
instruction: undefined,
|
||||||
|
estimatedTime: undefined,
|
||||||
totalScore: undefined,
|
totalScore: undefined,
|
||||||
passScore: undefined,
|
passScore: undefined,
|
||||||
|
allowAnonymous: false,
|
||||||
status: undefined
|
status: undefined
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
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>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<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-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<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>
|
<el-button @click="partDialogVisible = false">取消</el-button>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@ -206,7 +206,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { QuestionApi, Question } from '@/api/prison/question'
|
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 { Folder, InfoFilled, Rank, Plus, Delete } from '@element-plus/icons-vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
|
|
||||||
|
|||||||
@ -110,10 +110,10 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ dateFormatter(scope.row.createTime) }}
|
{{ formatDateTime(scope.row.createTime) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center" width="150">
|
<el-table-column label="操作" align="center" width="150" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -157,7 +157,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { formatDateTime } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { QuestionnaireApi, Questionnaire } from '@/api/prison/questionnaire'
|
import { QuestionnaireApi, Questionnaire } from '@/api/prison/questionnaire'
|
||||||
import QuestionnaireForm from './QuestionnaireForm.vue'
|
import QuestionnaireForm from './QuestionnaireForm.vue'
|
||||||
|
|||||||
@ -21,7 +21,13 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</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
|
<el-select
|
||||||
v-model="formData.prisonerIds"
|
v-model="formData.prisonerIds"
|
||||||
placeholder="请选择罪犯"
|
placeholder="请选择罪犯"
|
||||||
@ -93,6 +99,9 @@ const formData = reactive({
|
|||||||
remark: ''
|
remark: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 预选罪犯信息
|
||||||
|
const preselectedPrisoner = ref<{ id: number; prisonerNo: string; name: string } | null>(null)
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
questionnaireId: [{ required: true, message: '请选择问卷', trigger: 'change' }],
|
questionnaireId: [{ required: true, message: '请选择问卷', trigger: 'change' }],
|
||||||
prisonerIds: [{ required: true, message: '请选择罪犯', trigger: 'change' }]
|
prisonerIds: [{ required: true, message: '请选择罪犯', trigger: 'change' }]
|
||||||
@ -101,7 +110,7 @@ const rules = {
|
|||||||
/** 获取问卷列表 */
|
/** 获取问卷列表 */
|
||||||
const getQuestionnaireList = async () => {
|
const getQuestionnaireList = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await QuestionnaireApi.getQuestionnairePage({ pageNo: 1, pageSize: 1000 })
|
const data = await QuestionnaireApi.getQuestionnairePage({ pageNo: 1, pageSize: 200 })
|
||||||
questionnaireList.value = data.list
|
questionnaireList.value = data.list
|
||||||
} catch {}
|
} 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
|
dialogVisible.value = true
|
||||||
title.value = '发起测评'
|
title.value = '发起测评'
|
||||||
// 重置表单
|
// 重置表单
|
||||||
formData.questionnaireId = undefined
|
formData.questionnaireId = undefined
|
||||||
formData.prisonerIds = []
|
|
||||||
formData.deadline = ''
|
formData.deadline = ''
|
||||||
formData.remark = ''
|
formData.remark = ''
|
||||||
|
|
||||||
|
// 设置预选罪犯
|
||||||
|
if (prisoner) {
|
||||||
|
preselectedPrisoner.value = prisoner
|
||||||
|
formData.prisonerIds = [prisoner.id]
|
||||||
|
} else {
|
||||||
|
preselectedPrisoner.value = null
|
||||||
|
formData.prisonerIds = []
|
||||||
|
}
|
||||||
|
|
||||||
// 加载问卷列表
|
// 加载问卷列表
|
||||||
getQuestionnaireList()
|
getQuestionnaireList()
|
||||||
// 清空罪犯列表,等待用户搜索
|
// 如果没有预选罪犯,清空罪犯列表,等待用户搜索
|
||||||
prisonerList.value = []
|
if (!prisoner) {
|
||||||
|
prisonerList.value = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 提交 */
|
/** 提交 */
|
||||||
|
|||||||
@ -153,10 +153,13 @@
|
|||||||
label="创建时间"
|
label="创建时间"
|
||||||
align="center"
|
align="center"
|
||||||
prop="createTime"
|
prop="createTime"
|
||||||
:formatter="dateFormatter"
|
|
||||||
width="160"
|
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">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="scope.row.status === 1"
|
v-if="scope.row.status === 1"
|
||||||
@ -235,7 +238,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { isEmpty } from '@/utils/is'
|
import { isEmpty } from '@/utils/is'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { formatDateTime } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { QuestionnaireRecordApi, QuestionnaireRecord, QuestionnaireRecordPageParams } from '@/api/prison/questionnairerecord'
|
import { QuestionnaireRecordApi, QuestionnaireRecord, QuestionnaireRecordPageParams } from '@/api/prison/questionnairerecord'
|
||||||
import { QuestionnaireApi } from '@/api/prison/questionnaire'
|
import { QuestionnaireApi } from '@/api/prison/questionnaire'
|
||||||
@ -286,7 +289,7 @@ const getList = async () => {
|
|||||||
/** 获取问卷列表 */
|
/** 获取问卷列表 */
|
||||||
const getQuestionnaireList = async () => {
|
const getQuestionnaireList = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await QuestionnaireApi.getQuestionnairePage({ pageNo: 1, pageSize: 1000 })
|
const data = await QuestionnaireApi.getQuestionnairePage({ pageNo: 1, pageSize: 200 })
|
||||||
questionnaireList.value = data.list
|
questionnaireList.value = data.list
|
||||||
} catch {}
|
} 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>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>释放登记列表</span>
|
<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" /> 新增释放登记
|
<Icon icon="ep:plus" class="mr-5px" /> 新增释放登记
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@ -95,7 +95,7 @@
|
|||||||
<el-table-column prop="createTime" label="创建时间" width="160" />
|
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||||
<el-table-column label="操作" width="200" fixed="right">
|
<el-table-column label="操作" width="200" fixed="right">
|
||||||
<template #default="{ row }">
|
<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>
|
||||||
<el-button
|
<el-button
|
||||||
@ -103,7 +103,7 @@
|
|||||||
type="primary"
|
type="primary"
|
||||||
@click="handleEdit(row)"
|
@click="handleEdit(row)"
|
||||||
v-if="row.status === 1"
|
v-if="row.status === 1"
|
||||||
v-hasPermi="'prison:release:update'"
|
v-hasPermi="['prison:release:update']"
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -112,7 +112,7 @@
|
|||||||
type="success"
|
type="success"
|
||||||
@click="handleDoRelease(row)"
|
@click="handleDoRelease(row)"
|
||||||
v-if="row.status === 1"
|
v-if="row.status === 1"
|
||||||
v-hasPermi="'prison:release:update'"
|
v-hasPermi="['prison:release:update']"
|
||||||
>
|
>
|
||||||
执行释放
|
执行释放
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -121,7 +121,7 @@
|
|||||||
type="warning"
|
type="warning"
|
||||||
@click="handleCancel(row)"
|
@click="handleCancel(row)"
|
||||||
v-if="row.status === 1"
|
v-if="row.status === 1"
|
||||||
v-hasPermi="'prison:release:update'"
|
v-hasPermi="['prison:release:update']"
|
||||||
>
|
>
|
||||||
取消
|
取消
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -130,7 +130,7 @@
|
|||||||
type="danger"
|
type="danger"
|
||||||
@click="handleDelete(row)"
|
@click="handleDelete(row)"
|
||||||
v-if="row.status === 1"
|
v-if="row.status === 1"
|
||||||
v-hasPermi="'prison:release:delete'"
|
v-hasPermi="['prison:release:delete']"
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -153,16 +153,18 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ReleaseForm } from './ReleaseForm.vue'
|
||||||
import ReleaseForm from './ReleaseForm.vue'
|
import { ReleaseApi, type ReleasePageReqVO, type Release } from '@/api/prison/release'
|
||||||
import { ReleaseApi, type ReleasePageReqVO } from '@/api/prison/release'
|
|
||||||
|
|
||||||
defineOptions({ name: 'PrisonRelease' })
|
defineOptions({ name: 'PrisonRelease' })
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const message = useMessage() // 统一使用 useMessage
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const tableData = ref([])
|
const tableData = ref<Release[]>([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const dateRange = ref([])
|
const dateRange = ref<string[]>([])
|
||||||
|
|
||||||
const searchForm = reactive<ReleasePageReqVO>({
|
const searchForm = reactive<ReleasePageReqVO>({
|
||||||
prisonerNo: '',
|
prisonerNo: '',
|
||||||
@ -208,7 +210,7 @@ const handleReset = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 日期范围变化 */
|
/** 日期范围变化 */
|
||||||
const handleDateChange = (val: any) => {
|
const handleDateChange = (val: string[] | null) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
searchForm.actualReleaseDateStart = val[0]
|
searchForm.actualReleaseDateStart = val[0]
|
||||||
searchForm.actualReleaseDateEnd = val[1]
|
searchForm.actualReleaseDateEnd = val[1]
|
||||||
@ -224,46 +226,43 @@ const handleCreate = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑 */
|
/** 编辑 */
|
||||||
const handleEdit = (row: any) => {
|
const handleEdit = (row: Release) => {
|
||||||
formRef.value.open(row.id)
|
formRef.value.open(row.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 详情 */
|
/** 详情 */
|
||||||
const handleDetail = (row: any) => {
|
const handleDetail = (row: Release) => {
|
||||||
formRef.value.open(row.id, true)
|
formRef.value.open(row.id, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 执行释放 */
|
/** 执行释放 */
|
||||||
const handleDoRelease = (row: any) => {
|
const handleDoRelease = async (row: Release) => {
|
||||||
ElMessageBox.confirm(`确认要执行释放罪犯【${row.prisonerName}】吗?`, '操作确认', {
|
try {
|
||||||
type: 'warning'
|
await message.confirm(`确认要执行释放罪犯【${row.prisonerName}】吗?`)
|
||||||
}).then(async () => {
|
|
||||||
await ReleaseApi.doRelease(row.id)
|
await ReleaseApi.doRelease(row.id)
|
||||||
ElMessage.success('执行释放成功')
|
message.success('执行释放成功')
|
||||||
getPage()
|
getPage()
|
||||||
})
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 取消释放 */
|
/** 取消释放 */
|
||||||
const handleCancel = (row: any) => {
|
const handleCancel = async (row: Release) => {
|
||||||
ElMessageBox.confirm(`确认要取消释放登记吗?`, '操作确认', {
|
try {
|
||||||
type: 'warning'
|
await message.confirm('确认要取消释放登记吗?')
|
||||||
}).then(async () => {
|
|
||||||
await ReleaseApi.cancelRelease(row.id)
|
await ReleaseApi.cancelRelease(row.id)
|
||||||
ElMessage.success('取消释放成功')
|
message.success('取消释放成功')
|
||||||
getPage()
|
getPage()
|
||||||
})
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除 */
|
/** 删除 */
|
||||||
const handleDelete = (row: any) => {
|
const handleDelete = async (row: Release) => {
|
||||||
ElMessageBox.confirm(`确认要删除该释放登记吗?`, '操作确认', {
|
try {
|
||||||
type: 'warning'
|
await message.confirm('确认要删除该释放登记吗?')
|
||||||
}).then(async () => {
|
|
||||||
await ReleaseApi.deleteRelease(row.id)
|
await ReleaseApi.deleteRelease(row.id)
|
||||||
ElMessage.success('删除成功')
|
message.success('删除成功')
|
||||||
getPage()
|
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"
|
label-width="100px"
|
||||||
v-loading="formLoading"
|
v-loading="formLoading"
|
||||||
>
|
>
|
||||||
<el-form-item label="罪犯ID" prop="prisonerId">
|
<el-form-item label="罪犯" prop="prisonerId">
|
||||||
<el-input v-model="formData.prisonerId" placeholder="请输入罪犯ID" />
|
<el-select
|
||||||
</el-form-item>
|
v-model="formData.prisonerId"
|
||||||
<el-form-item label="罪犯编号" prop="prisonerNo">
|
placeholder="请选择罪犯"
|
||||||
<el-input v-model="formData.prisonerNo" 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>
|
||||||
<el-form-item label="评估类型" prop="assessmentType">
|
<el-form-item label="评估类型" prop="assessmentType">
|
||||||
<el-select v-model="formData.assessmentType" placeholder="请选择评估类型">
|
<el-select v-model="formData.assessmentType" placeholder="请选择评估类型">
|
||||||
@ -95,6 +108,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { RiskAssessmentApi, RiskAssessment } from '@/api/prison/riskassessment'
|
import { RiskAssessmentApi, RiskAssessment } from '@/api/prison/riskassessment'
|
||||||
|
import { PrisonerApi, PrisonerVO } from '@/api/prison/prisoner'
|
||||||
|
|
||||||
/** 危险评估 表单 */
|
/** 危险评估 表单 */
|
||||||
defineOptions({ name: 'RiskAssessmentForm' })
|
defineOptions({ name: 'RiskAssessmentForm' })
|
||||||
@ -106,35 +120,69 @@ const dialogVisible = ref(false) // 弹窗的是否展示
|
|||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
|
|
||||||
|
// 罪犯选择相关
|
||||||
|
const prisonerLoading = ref(false)
|
||||||
|
const prisonerList = ref<PrisonerVO[]>([])
|
||||||
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
prisonerId: undefined,
|
prisonerId: undefined as number | undefined,
|
||||||
prisonerNo: undefined,
|
prisonerNo: undefined as string | undefined,
|
||||||
assessmentType: undefined,
|
assessmentType: undefined as number | undefined,
|
||||||
assessmentDate: undefined,
|
assessmentDate: undefined as number | undefined,
|
||||||
violenceScore: undefined,
|
violenceScore: undefined as number | undefined,
|
||||||
escapeScore: undefined,
|
escapeScore: undefined as number | undefined,
|
||||||
suicideScore: undefined,
|
suicideScore: undefined as number | undefined,
|
||||||
totalScore: undefined,
|
totalScore: undefined as number | undefined,
|
||||||
riskLevel: undefined,
|
riskLevel: undefined as number | undefined,
|
||||||
riskFactors: undefined,
|
riskFactors: undefined as string | undefined,
|
||||||
suggestions: undefined,
|
suggestions: undefined as string | undefined,
|
||||||
assessorId: undefined,
|
assessorId: undefined as number | undefined,
|
||||||
assessorName: undefined,
|
assessorName: undefined as string | undefined,
|
||||||
nextAssessmentDate: undefined,
|
nextAssessmentDate: undefined as number | undefined,
|
||||||
status: undefined,
|
status: 1 as number | undefined,
|
||||||
remark: undefined
|
remark: undefined as string | undefined
|
||||||
})
|
})
|
||||||
const formRules = reactive({
|
const formRules = reactive({
|
||||||
prisonerId: [{ required: true, message: '罪犯ID不能为空', trigger: 'blur' }],
|
prisonerId: [{ required: true, message: '罪犯不能为空', trigger: 'change' }],
|
||||||
prisonerNo: [{ required: true, message: '罪犯编号不能为空', trigger: 'blur' }],
|
|
||||||
assessmentType: [{ 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' }],
|
riskLevel: [{ required: true, message: '风险等级不能为空', trigger: 'change' }],
|
||||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
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) => {
|
const open = async (type: string, id?: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
@ -146,6 +194,11 @@ const open = async (type: string, id?: number) => {
|
|||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
formData.value = await RiskAssessmentApi.getRiskAssessment(id)
|
formData.value = await RiskAssessmentApi.getRiskAssessment(id)
|
||||||
|
// 加载已选罪犯信息
|
||||||
|
if (formData.value.prisonerId) {
|
||||||
|
const prisoner = await PrisonerApi.getPrisoner(formData.value.prisonerId)
|
||||||
|
prisonerList.value = [prisoner]
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
@ -195,9 +248,10 @@ const resetForm = () => {
|
|||||||
assessorId: undefined,
|
assessorId: undefined,
|
||||||
assessorName: undefined,
|
assessorName: undefined,
|
||||||
nextAssessmentDate: undefined,
|
nextAssessmentDate: undefined,
|
||||||
status: undefined,
|
status: 1,
|
||||||
remark: undefined
|
remark: undefined
|
||||||
}
|
}
|
||||||
|
prisonerList.value = []
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -14,7 +14,16 @@
|
|||||||
placeholder="请输入罪犯编号"
|
placeholder="请输入罪犯编号"
|
||||||
clearable
|
clearable
|
||||||
@keyup.enter="handleQuery"
|
@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>
|
||||||
<el-form-item label="评估类型" prop="assessmentType">
|
<el-form-item label="评估类型" prop="assessmentType">
|
||||||
@ -105,12 +114,17 @@
|
|||||||
<el-table-column type="selection" width="55" />
|
<el-table-column type="selection" width="55" />
|
||||||
<el-table-column label="评估ID" align="center" prop="id" width="80" />
|
<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="prisonerNo" width="120" />
|
||||||
|
<el-table-column label="罪犯姓名" align="center" prop="prisonerName" width="100" />
|
||||||
<el-table-column label="评估类型" align="center" prop="assessmentType" width="100">
|
<el-table-column label="评估类型" align="center" prop="assessmentType" width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<dict-tag :type="DICT_TYPE.PRISON_ASSESSMENT_TYPE" :value="scope.row.assessmentType" />
|
<dict-tag :type="DICT_TYPE.PRISON_ASSESSMENT_TYPE" :value="scope.row.assessmentType" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</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="violenceScore" width="90" />
|
||||||
<el-table-column label="脱逃得分" align="center" prop="escapeScore" width="90" />
|
<el-table-column label="脱逃得分" align="center" prop="escapeScore" width="90" />
|
||||||
<el-table-column label="自杀得分" align="center" prop="suicideScore" width="90" />
|
<el-table-column label="自杀得分" align="center" prop="suicideScore" width="90" />
|
||||||
@ -129,10 +143,10 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ dateFormatter(scope.row.createTime) }}
|
{{ formatDateTime(scope.row.createTime) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center" width="120">
|
<el-table-column label="操作" align="center" width="120" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -167,7 +181,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { formatDateTime } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { RiskAssessmentApi, RiskAssessment } from '@/api/prison/riskassessment'
|
import { RiskAssessmentApi, RiskAssessment } from '@/api/prison/riskassessment'
|
||||||
import RiskAssessmentForm from './RiskAssessmentForm.vue'
|
import RiskAssessmentForm from './RiskAssessmentForm.vue'
|
||||||
@ -184,6 +198,7 @@ const queryParams = reactive({
|
|||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
prisonerNo: undefined,
|
prisonerNo: undefined,
|
||||||
|
prisonerName: undefined,
|
||||||
assessmentType: undefined,
|
assessmentType: undefined,
|
||||||
riskLevel: undefined,
|
riskLevel: undefined,
|
||||||
status: undefined
|
status: undefined
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>考核规则配置</span>
|
<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" /> 新增规则
|
<Icon icon="ep:plus" class="mr-5px" /> 新增规则
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@ -78,10 +78,10 @@
|
|||||||
<el-table-column prop="sort" label="排序" width="80" />
|
<el-table-column prop="sort" label="排序" width="80" />
|
||||||
<el-table-column label="操作" width="150" fixed="right">
|
<el-table-column label="操作" width="150" fixed="right">
|
||||||
<template #default="{ row }">
|
<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>
|
||||||
<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>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -7,32 +7,58 @@
|
|||||||
label-width="100px"
|
label-width="100px"
|
||||||
v-loading="formLoading"
|
v-loading="formLoading"
|
||||||
>
|
>
|
||||||
<el-form-item label="罪犯ID" prop="prisonerId">
|
<el-form-item label="罪犯" prop="prisonerId">
|
||||||
<el-input v-model="formData.prisonerId" placeholder="请输入罪犯ID" />
|
<el-select
|
||||||
</el-form-item>
|
v-model="formData.prisonerId"
|
||||||
<el-form-item label="罪犯编号" prop="prisonerNo">
|
placeholder="请选择罪犯"
|
||||||
<el-input v-model="formData.prisonerNo" 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>
|
||||||
<el-form-item label="考核年份" prop="year">
|
<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>
|
||||||
<el-form-item label="考核月份" prop="month">
|
<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>
|
||||||
<el-form-item label="基础分" prop="baseScore">
|
<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>
|
||||||
<el-form-item label="加分" prop="rewardScore">
|
<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>
|
||||||
<el-form-item label="扣分" prop="penaltyScore">
|
<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>
|
||||||
<el-form-item label="总分" prop="totalScore">
|
<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>
|
||||||
<el-form-item label="考核等级" prop="level">
|
<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
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_SCORE_LEVEL)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_SCORE_LEVEL)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@ -41,12 +67,6 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</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-form-item label="状态" prop="status">
|
||||||
<el-radio-group v-model="formData.status">
|
<el-radio-group v-model="formData.status">
|
||||||
<el-radio
|
<el-radio
|
||||||
@ -69,6 +89,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { ScoreApi, Score } from '@/api/prison/score'
|
import { ScoreApi, Score } from '@/api/prison/score'
|
||||||
|
import { PrisonerApi, PrisonerVO } from '@/api/prison/prisoner'
|
||||||
|
|
||||||
/** 计分考核 表单 */
|
/** 计分考核 表单 */
|
||||||
defineOptions({ name: 'ScoreForm' })
|
defineOptions({ name: 'ScoreForm' })
|
||||||
@ -80,31 +101,65 @@ const dialogVisible = ref(false) // 弹窗的是否展示
|
|||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
|
|
||||||
|
// 罪犯选择相关
|
||||||
|
const prisonerLoading = ref(false)
|
||||||
|
const prisonerList = ref<PrisonerVO[]>([])
|
||||||
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
prisonerId: undefined,
|
prisonerId: undefined as number | undefined,
|
||||||
prisonerNo: undefined,
|
prisonerNo: undefined as string | undefined,
|
||||||
year: undefined,
|
year: undefined as string | undefined,
|
||||||
month: undefined,
|
month: undefined as number | undefined,
|
||||||
baseScore: undefined,
|
baseScore: 0,
|
||||||
rewardScore: undefined,
|
rewardScore: 0,
|
||||||
penaltyScore: undefined,
|
penaltyScore: 0,
|
||||||
totalScore: undefined,
|
totalScore: 0,
|
||||||
level: undefined,
|
level: undefined as number | undefined,
|
||||||
assessorId: undefined,
|
assessorId: undefined as number | undefined,
|
||||||
assessorName: undefined,
|
assessorName: undefined as string | undefined,
|
||||||
status: undefined,
|
status: 1,
|
||||||
remark: undefined
|
remark: undefined as string | undefined
|
||||||
})
|
})
|
||||||
const formRules = reactive({
|
const formRules = reactive({
|
||||||
prisonerId: [{ required: true, message: '罪犯ID不能为空', trigger: 'blur' }],
|
prisonerId: [{ required: true, message: '罪犯不能为空', trigger: 'change' }],
|
||||||
prisonerNo: [{ required: true, message: '罪犯编号不能为空', trigger: 'blur' }],
|
year: [{ required: true, message: '考核年份不能为空', trigger: 'change' }],
|
||||||
year: [{ required: true, message: '考核年份不能为空', trigger: 'blur' }],
|
month: [{ required: true, message: '考核月份不能为空', trigger: 'change' }],
|
||||||
month: [{ required: true, message: '考核月份不能为空', trigger: 'blur' }],
|
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
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) => {
|
const open = async (type: string, id?: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
@ -115,7 +170,13 @@ const open = async (type: string, id?: number) => {
|
|||||||
if (id) {
|
if (id) {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
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 {
|
} finally {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
@ -155,16 +216,17 @@ const resetForm = () => {
|
|||||||
prisonerNo: undefined,
|
prisonerNo: undefined,
|
||||||
year: undefined,
|
year: undefined,
|
||||||
month: undefined,
|
month: undefined,
|
||||||
baseScore: undefined,
|
baseScore: 0,
|
||||||
rewardScore: undefined,
|
rewardScore: 0,
|
||||||
penaltyScore: undefined,
|
penaltyScore: 0,
|
||||||
totalScore: undefined,
|
totalScore: 0,
|
||||||
level: undefined,
|
level: undefined,
|
||||||
assessorId: undefined,
|
assessorId: undefined,
|
||||||
assessorName: undefined,
|
assessorName: undefined,
|
||||||
status: undefined,
|
status: 1,
|
||||||
remark: undefined
|
remark: undefined
|
||||||
}
|
}
|
||||||
|
prisonerList.value = []
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -17,6 +17,15 @@
|
|||||||
class="!w-140px"
|
class="!w-140px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</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-form-item label="考核年份" prop="year">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.year"
|
v-model="queryParams.year"
|
||||||
@ -98,7 +107,10 @@
|
|||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" />
|
<el-table-column type="selection" width="55" />
|
||||||
<el-table-column label="记录ID" align="center" prop="id" width="80" />
|
<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="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="year" width="80" />
|
||||||
<el-table-column label="月份" align="center" prop="month" width="70" />
|
<el-table-column label="月份" align="center" prop="month" width="70" />
|
||||||
<el-table-column label="基础分" align="center" prop="baseScore" width="80" />
|
<el-table-column label="基础分" align="center" prop="baseScore" width="80" />
|
||||||
@ -118,10 +130,10 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ dateFormatter(scope.row.createTime) }}
|
{{ formatDateTime(scope.row.createTime) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center" width="120">
|
<el-table-column label="操作" align="center" width="120" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -156,7 +168,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { formatDateTime } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { ScoreApi, Score } from '@/api/prison/score'
|
import { ScoreApi, Score } from '@/api/prison/score'
|
||||||
import ScoreForm from './ScoreForm.vue'
|
import ScoreForm from './ScoreForm.vue'
|
||||||
@ -173,6 +185,7 @@ const queryParams = reactive({
|
|||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
prisonerNo: undefined,
|
prisonerNo: undefined,
|
||||||
|
prisonerName: undefined,
|
||||||
year: undefined,
|
year: undefined,
|
||||||
level: undefined,
|
level: undefined,
|
||||||
status: 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