fix(performance): 批量更新接口优化问卷系统性能
- 添加batchUpdate API调用 - 修复savePartitions循环调用API问题 (50题只需1次请求) - 修复onPartitionDragEnd拖拽排序性能问题 - 修复onQuestionDragEnd问题拖拽排序性能问题 - 添加自动填充来源字典支持 PRISON_QUESTION_AUTO_FILL_SOURCE - 问题表单优化: 折叠面板、分区选择、快速粘贴等
This commit is contained in:
parent
35af632010
commit
d4cb996085
53
src/api/prison/area/index.ts
Normal file
53
src/api/prison/area/index.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import request from '@/config/axios'
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
/** 监区信息信息 */
|
||||
export interface Area {
|
||||
id: number; // 监区ID
|
||||
name?: string; // 监区名称
|
||||
code?: string; // 监区编码
|
||||
type: number; // 监区类型:1-普通监区 2-严管监区 3-医院 4-禁闭室
|
||||
capacity: number; // 容纳人数
|
||||
currentCount: number; // 当前人数
|
||||
sort: number; // 排序
|
||||
status?: number; // 状态:1-启用 2-禁用
|
||||
remark: string; // 备注
|
||||
}
|
||||
|
||||
// 监区信息 API
|
||||
export const AreaApi = {
|
||||
// 查询监区信息分页
|
||||
getAreaPage: async (params: any) => {
|
||||
return await request.get({ url: `/prison/area/page`, params })
|
||||
},
|
||||
|
||||
// 查询监区信息详情
|
||||
getArea: async (id: number) => {
|
||||
return await request.get({ url: `/prison/area/get?id=` + id })
|
||||
},
|
||||
|
||||
// 新增监区信息
|
||||
createArea: async (data: Area) => {
|
||||
return await request.post({ url: `/prison/area/create`, data })
|
||||
},
|
||||
|
||||
// 修改监区信息
|
||||
updateArea: async (data: Area) => {
|
||||
return await request.put({ url: `/prison/area/update`, data })
|
||||
},
|
||||
|
||||
// 删除监区信息
|
||||
deleteArea: async (id: number) => {
|
||||
return await request.delete({ url: `/prison/area/delete?id=` + id })
|
||||
},
|
||||
|
||||
/** 批量删除监区信息 */
|
||||
deleteAreaList: async (ids: number[]) => {
|
||||
return await request.delete({ url: `/prison/area/delete-list?ids=${ids.join(',')}` })
|
||||
},
|
||||
|
||||
// 导出监区信息 Excel
|
||||
exportArea: async (params) => {
|
||||
return await request.download({ url: `/prison/area/export-excel`, params })
|
||||
}
|
||||
}
|
||||
53
src/api/prison/cell/index.ts
Normal file
53
src/api/prison/cell/index.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import request from '@/config/axios'
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
/** 监室信息信息 */
|
||||
export interface Cell {
|
||||
id: number; // 监室ID
|
||||
areaId?: number; // 所属监区ID
|
||||
name?: string; // 监室名称
|
||||
code?: string; // 监室编码
|
||||
capacity: number; // 床位数量
|
||||
currentCount: number; // 当前人数
|
||||
sort: number; // 排序
|
||||
status?: number; // 状态:1-启用 2-禁用
|
||||
remark: string; // 备注
|
||||
}
|
||||
|
||||
// 监室信息 API
|
||||
export const CellApi = {
|
||||
// 查询监室信息分页
|
||||
getCellPage: async (params: any) => {
|
||||
return await request.get({ url: `/prison/cell/page`, params })
|
||||
},
|
||||
|
||||
// 查询监室信息详情
|
||||
getCell: async (id: number) => {
|
||||
return await request.get({ url: `/prison/cell/get?id=` + id })
|
||||
},
|
||||
|
||||
// 新增监室信息
|
||||
createCell: async (data: Cell) => {
|
||||
return await request.post({ url: `/prison/cell/create`, data })
|
||||
},
|
||||
|
||||
// 修改监室信息
|
||||
updateCell: async (data: Cell) => {
|
||||
return await request.put({ url: `/prison/cell/update`, data })
|
||||
},
|
||||
|
||||
// 删除监室信息
|
||||
deleteCell: async (id: number) => {
|
||||
return await request.delete({ url: `/prison/cell/delete?id=` + id })
|
||||
},
|
||||
|
||||
/** 批量删除监室信息 */
|
||||
deleteCellList: async (ids: number[]) => {
|
||||
return await request.delete({ url: `/prison/cell/delete-list?ids=${ids.join(',')}` })
|
||||
},
|
||||
|
||||
// 导出监室信息 Excel
|
||||
exportCell: async (params) => {
|
||||
return await request.download({ url: `/prison/cell/export-excel`, params })
|
||||
}
|
||||
}
|
||||
56
src/api/prison/consumption/index.ts
Normal file
56
src/api/prison/consumption/index.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import request from '@/config/axios'
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
/** 消费记录信息 */
|
||||
export interface Consumption {
|
||||
id: number; // 记录ID
|
||||
prisonerId?: number; // 罪犯ID
|
||||
prisonerNo?: string; // 罪犯编号
|
||||
type?: number; // 类型:1-存款 2-消费 3-转账
|
||||
amount?: number; // 金额
|
||||
balance: number; // 账户余额
|
||||
goodsName: string; // 商品名称
|
||||
goodsCount: number; // 商品数量
|
||||
orderNo: string; // 订单号
|
||||
tradeTime?: string | Dayjs; // 交易时间
|
||||
status?: number; // 状态:1-成功 2-失败
|
||||
remark: string; // 备注
|
||||
}
|
||||
|
||||
// 消费记录 API
|
||||
export const ConsumptionApi = {
|
||||
// 查询消费记录分页
|
||||
getConsumptionPage: async (params: any) => {
|
||||
return await request.get({ url: `/prison/consumption/page`, params })
|
||||
},
|
||||
|
||||
// 查询消费记录详情
|
||||
getConsumption: async (id: number) => {
|
||||
return await request.get({ url: `/prison/consumption/get?id=` + id })
|
||||
},
|
||||
|
||||
// 新增消费记录
|
||||
createConsumption: async (data: Consumption) => {
|
||||
return await request.post({ url: `/prison/consumption/create`, data })
|
||||
},
|
||||
|
||||
// 修改消费记录
|
||||
updateConsumption: async (data: Consumption) => {
|
||||
return await request.put({ url: `/prison/consumption/update`, data })
|
||||
},
|
||||
|
||||
// 删除消费记录
|
||||
deleteConsumption: async (id: number) => {
|
||||
return await request.delete({ url: `/prison/consumption/delete?id=` + id })
|
||||
},
|
||||
|
||||
/** 批量删除消费记录 */
|
||||
deleteConsumptionList: async (ids: number[]) => {
|
||||
return await request.delete({ url: `/prison/consumption/delete-list?ids=${ids.join(',')}` })
|
||||
},
|
||||
|
||||
// 导出消费记录 Excel
|
||||
exportConsumption: async (params) => {
|
||||
return await request.download({ url: `/prison/consumption/export-excel`, params })
|
||||
}
|
||||
}
|
||||
69
src/api/prison/question/index.ts
Normal file
69
src/api/prison/question/index.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import request from '@/config/axios'
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
/** 问卷问题信息 */
|
||||
export interface Question {
|
||||
id: number // 问题ID
|
||||
questionnaireId?: number // 所属问卷ID
|
||||
title?: string // 问题标题
|
||||
type?: number // 问题类型:1-单选 2-多选 3-填空 4-评分 5-日期 6-数字
|
||||
options?: string // 选项JSON
|
||||
score?: number // 分值
|
||||
sort?: number // 排序
|
||||
isRequired?: boolean // 是否必答
|
||||
// 新增字段
|
||||
partName?: string // 分区名称
|
||||
partSort?: number // 分区排序
|
||||
helpText?: string // 帮助说明
|
||||
placeholder?: string // 占位提示
|
||||
defaultValue?: string // 默认值
|
||||
autoFillType?: string // 自动填充类型:NONE/AUTO/MANUAL
|
||||
autoFillSource?: string // 自动填充来源
|
||||
displayCondition?: string // 显示条件JSON
|
||||
minValue?: number // 最小值
|
||||
maxValue?: number // 最大值
|
||||
createTime?: Date // 创建时间
|
||||
}
|
||||
|
||||
// 问卷问题 API
|
||||
export const QuestionApi = {
|
||||
// 查询问卷问题分页
|
||||
getQuestionPage: async (params: any) => {
|
||||
return await request.get({ url: `/prison/question/page`, params })
|
||||
},
|
||||
|
||||
// 查询问卷问题详情
|
||||
getQuestion: async (id: number) => {
|
||||
return await request.get({ url: `/prison/question/get?id=` + id })
|
||||
},
|
||||
|
||||
// 新增问卷问题
|
||||
createQuestion: async (data: Question) => {
|
||||
return await request.post({ url: `/prison/question/create`, data })
|
||||
},
|
||||
|
||||
// 修改问卷问题
|
||||
updateQuestion: async (data: Question) => {
|
||||
return await request.put({ url: `/prison/question/update`, data })
|
||||
},
|
||||
|
||||
// 删除问卷问题
|
||||
deleteQuestion: async (id: number) => {
|
||||
return await request.delete({ url: `/prison/question/delete?id=` + id })
|
||||
},
|
||||
|
||||
/** 批量删除问卷问题 */
|
||||
deleteQuestionList: async (ids: number[]) => {
|
||||
return await request.delete({ url: `/prison/question/delete-list?ids=${ids.join(',')}` })
|
||||
},
|
||||
|
||||
// 导出问卷问题 Excel
|
||||
exportQuestion: async (params) => {
|
||||
return await request.download({ url: `/prison/question/export-excel`, params })
|
||||
},
|
||||
|
||||
// 批量更新问卷问题(仅排序和分区字段)
|
||||
batchUpdate: async (data: { questions: Array<{ id: number; partName?: string; partSort?: number; sort?: number }> }) => {
|
||||
return await request.post({ url: `/prison/question/batch-update`, data })
|
||||
}
|
||||
}
|
||||
51
src/api/prison/questionnaire/index.ts
Normal file
51
src/api/prison/questionnaire/index.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import request from '@/config/axios'
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
/** 问卷模板信息 */
|
||||
export interface Questionnaire {
|
||||
id: number; // 问卷ID
|
||||
title?: string; // 问卷标题
|
||||
type?: number; // 问卷类型:1-心理测评 2-行为评估 3-满意度调查
|
||||
description: string; // 问卷说明
|
||||
totalScore: number; // 总分
|
||||
passScore: number; // 及格分
|
||||
status?: number; // 状态:1-草稿 2-已发布 3-已禁用
|
||||
}
|
||||
|
||||
// 问卷模板 API
|
||||
export const QuestionnaireApi = {
|
||||
// 查询问卷模板分页
|
||||
getQuestionnairePage: async (params: any) => {
|
||||
return await request.get({ url: `/prison/questionnaire/page`, params })
|
||||
},
|
||||
|
||||
// 查询问卷模板详情
|
||||
getQuestionnaire: async (id: number) => {
|
||||
return await request.get({ url: `/prison/questionnaire/get?id=` + id })
|
||||
},
|
||||
|
||||
// 新增问卷模板
|
||||
createQuestionnaire: async (data: Questionnaire) => {
|
||||
return await request.post({ url: `/prison/questionnaire/create`, data })
|
||||
},
|
||||
|
||||
// 修改问卷模板
|
||||
updateQuestionnaire: async (data: Questionnaire) => {
|
||||
return await request.put({ url: `/prison/questionnaire/update`, data })
|
||||
},
|
||||
|
||||
// 删除问卷模板
|
||||
deleteQuestionnaire: async (id: number) => {
|
||||
return await request.delete({ url: `/prison/questionnaire/delete?id=` + id })
|
||||
},
|
||||
|
||||
/** 批量删除问卷模板 */
|
||||
deleteQuestionnaireList: async (ids: number[]) => {
|
||||
return await request.delete({ url: `/prison/questionnaire/delete-list?ids=${ids.join(',')}` })
|
||||
},
|
||||
|
||||
// 导出问卷模板 Excel
|
||||
exportQuestionnaire: async (params) => {
|
||||
return await request.download({ url: `/prison/questionnaire/export-excel`, params })
|
||||
}
|
||||
}
|
||||
52
src/api/prison/questionnairerecord/index.ts
Normal file
52
src/api/prison/questionnairerecord/index.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import request from '@/config/axios'
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
/** 问卷答题记录信息 */
|
||||
export interface QuestionnaireRecord {
|
||||
id: number; // 记录ID
|
||||
questionnaireId?: number; // 问卷ID
|
||||
prisonerId?: number; // 罪犯ID
|
||||
prisonerNo?: string; // 罪犯编号
|
||||
totalScore: number; // 得分
|
||||
passStatus: number; // 是否及格:1-及格 2-不及格
|
||||
answerTime?: string | Dayjs; // 答题时间
|
||||
status?: number; // 状态:1-已完成 2-已过期
|
||||
}
|
||||
|
||||
// 问卷答题记录 API
|
||||
export const QuestionnaireRecordApi = {
|
||||
// 查询问卷答题记录分页
|
||||
getQuestionnaireRecordPage: async (params: any) => {
|
||||
return await request.get({ url: `/prison/questionnaire-record/page`, params })
|
||||
},
|
||||
|
||||
// 查询问卷答题记录详情
|
||||
getQuestionnaireRecord: async (id: number) => {
|
||||
return await request.get({ url: `/prison/questionnaire-record/get?id=` + id })
|
||||
},
|
||||
|
||||
// 新增问卷答题记录
|
||||
createQuestionnaireRecord: async (data: QuestionnaireRecord) => {
|
||||
return await request.post({ url: `/prison/questionnaire-record/create`, data })
|
||||
},
|
||||
|
||||
// 修改问卷答题记录
|
||||
updateQuestionnaireRecord: async (data: QuestionnaireRecord) => {
|
||||
return await request.put({ url: `/prison/questionnaire-record/update`, data })
|
||||
},
|
||||
|
||||
// 删除问卷答题记录
|
||||
deleteQuestionnaireRecord: async (id: number) => {
|
||||
return await request.delete({ url: `/prison/questionnaire-record/delete?id=` + id })
|
||||
},
|
||||
|
||||
/** 批量删除问卷答题记录 */
|
||||
deleteQuestionnaireRecordList: async (ids: number[]) => {
|
||||
return await request.delete({ url: `/prison/questionnaire-record/delete-list?ids=${ids.join(',')}` })
|
||||
},
|
||||
|
||||
// 导出问卷答题记录 Excel
|
||||
exportQuestionnaireRecord: async (params) => {
|
||||
return await request.download({ url: `/prison/questionnaire-record/export-excel`, params })
|
||||
}
|
||||
}
|
||||
61
src/api/prison/riskassessment/index.ts
Normal file
61
src/api/prison/riskassessment/index.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import request from '@/config/axios'
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
/** 危险评估信息 */
|
||||
export interface RiskAssessment {
|
||||
id: number; // 评估ID
|
||||
prisonerId?: number; // 罪犯ID
|
||||
prisonerNo?: string; // 罪犯编号
|
||||
assessmentType?: number; // 评估类型:1-入狱评估 2-定期评估 3-专项评估
|
||||
assessmentDate?: string | Dayjs; // 评估日期
|
||||
violenceScore: number; // 暴力倾向得分
|
||||
escapeScore: number; // 脱逃倾向得分
|
||||
suicideScore: number; // 自杀倾向得分
|
||||
totalScore: number; // 综合得分
|
||||
riskLevel?: number; // 风险等级:1-低风险 2-中风险 3-高风险 4-极高风险
|
||||
riskFactors: string; // 风险因素
|
||||
suggestions: string; // 管控建议
|
||||
assessorId: number; // 评估人ID
|
||||
assessorName: string; // 评估人姓名
|
||||
nextAssessmentDate: string | Dayjs; // 下次评估日期
|
||||
status?: number; // 状态:1-待审核 2-已通过
|
||||
remark: string; // 备注
|
||||
}
|
||||
|
||||
// 危险评估 API
|
||||
export const RiskAssessmentApi = {
|
||||
// 查询危险评估分页
|
||||
getRiskAssessmentPage: async (params: any) => {
|
||||
return await request.get({ url: `/prison/risk-assessment/page`, params })
|
||||
},
|
||||
|
||||
// 查询危险评估详情
|
||||
getRiskAssessment: async (id: number) => {
|
||||
return await request.get({ url: `/prison/risk-assessment/get?id=` + id })
|
||||
},
|
||||
|
||||
// 新增危险评估
|
||||
createRiskAssessment: async (data: RiskAssessment) => {
|
||||
return await request.post({ url: `/prison/risk-assessment/create`, data })
|
||||
},
|
||||
|
||||
// 修改危险评估
|
||||
updateRiskAssessment: async (data: RiskAssessment) => {
|
||||
return await request.put({ url: `/prison/risk-assessment/update`, data })
|
||||
},
|
||||
|
||||
// 删除危险评估
|
||||
deleteRiskAssessment: async (id: number) => {
|
||||
return await request.delete({ url: `/prison/risk-assessment/delete?id=` + id })
|
||||
},
|
||||
|
||||
/** 批量删除危险评估 */
|
||||
deleteRiskAssessmentList: async (ids: number[]) => {
|
||||
return await request.delete({ url: `/prison/risk-assessment/delete-list?ids=${ids.join(',')}` })
|
||||
},
|
||||
|
||||
// 导出危险评估 Excel
|
||||
exportRiskAssessment: async (params) => {
|
||||
return await request.download({ url: `/prison/risk-assessment/export-excel`, params })
|
||||
}
|
||||
}
|
||||
58
src/api/prison/score/index.ts
Normal file
58
src/api/prison/score/index.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import request from '@/config/axios'
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
/** 计分考核信息 */
|
||||
export interface Score {
|
||||
id: number; // 记录ID
|
||||
prisonerId?: number; // 罪犯ID
|
||||
prisonerNo?: string; // 罪犯编号
|
||||
year?: number; // 考核年份
|
||||
month?: number; // 考核月份
|
||||
baseScore: number; // 基础分
|
||||
rewardScore: number; // 加分
|
||||
penaltyScore: number; // 扣分
|
||||
totalScore: number; // 总分
|
||||
level: number; // 考核等级:1-优秀 2-良好 3-合格 4-不合格
|
||||
assessorId: number; // 考核人ID
|
||||
assessorName: string; // 考核人姓名
|
||||
status?: number; // 状态:1-待审核 2-已通过 3-已驳回
|
||||
remark: string; // 备注
|
||||
}
|
||||
|
||||
// 计分考核 API
|
||||
export const ScoreApi = {
|
||||
// 查询计分考核分页
|
||||
getScorePage: async (params: any) => {
|
||||
return await request.get({ url: `/prison/score/page`, params })
|
||||
},
|
||||
|
||||
// 查询计分考核详情
|
||||
getScore: async (id: number) => {
|
||||
return await request.get({ url: `/prison/score/get?id=` + id })
|
||||
},
|
||||
|
||||
// 新增计分考核
|
||||
createScore: async (data: Score) => {
|
||||
return await request.post({ url: `/prison/score/create`, data })
|
||||
},
|
||||
|
||||
// 修改计分考核
|
||||
updateScore: async (data: Score) => {
|
||||
return await request.put({ url: `/prison/score/update`, data })
|
||||
},
|
||||
|
||||
// 删除计分考核
|
||||
deleteScore: async (id: number) => {
|
||||
return await request.delete({ url: `/prison/score/delete?id=` + id })
|
||||
},
|
||||
|
||||
/** 批量删除计分考核 */
|
||||
deleteScoreList: async (ids: number[]) => {
|
||||
return await request.delete({ url: `/prison/score/delete-list?ids=${ids.join(',')}` })
|
||||
},
|
||||
|
||||
// 导出计分考核 Excel
|
||||
exportScore: async (params) => {
|
||||
return await request.download({ url: `/prison/score/export-excel`, params })
|
||||
}
|
||||
}
|
||||
@ -253,5 +253,18 @@ export enum DICT_TYPE {
|
||||
PRISON_SUPERVISION_LEVEL = 'prison_supervision_level', // 监管等级
|
||||
PRISON_RISK_LEVEL = 'prison_risk_level', // 风险等级
|
||||
PRISONER_STATUS = 'prisoner_status', // 罪犯状态
|
||||
PRISON_EDUCATION = 'prison_education' // 文化程度
|
||||
PRISON_EDUCATION = 'prison_education', // 文化程度
|
||||
PRISON_QUESTION_TYPE = 'prison_question_type', // 问卷问题类型
|
||||
PRISON_QUESTIONNAIRE_TYPE = 'prison_questionnaire_type', // 问卷类型
|
||||
PRISON_QUESTIONNAIRE_STATUS = 'prison_questionnaire_status', // 问卷状态
|
||||
PRISON_CONSUMPTION_TYPE = 'prison_consumption_type', // 消费类型
|
||||
PRISON_CONSUMPTION_STATUS = 'prison_consumption_status', // 消费状态
|
||||
PRISON_ASSESSMENT_TYPE = 'prison_assessment_type', // 评估类型
|
||||
PRISON_SCORE_LEVEL = 'prison_score_level', // 考核等级
|
||||
PRISON_SCORE_STATUS = 'prison_score_status', // 考核状态
|
||||
PRISON_AREA_TYPE = 'prison_area_type', // 监区类型
|
||||
PRISON_CELL_STATUS = 'prison_cell_status', // 监室状态
|
||||
PRISON_RECORD_PASS_STATUS = 'prison_record_pass_status', // 问卷答题是否及格
|
||||
PRISON_RECORD_STATUS = 'prison_record_status', // 问卷答题记录状态
|
||||
PRISON_QUESTION_AUTO_FILL_SOURCE = 'prison_question_auto_fill_source' // 问卷问题自动填充来源
|
||||
}
|
||||
|
||||
143
src/views/prison/area/AreaForm.vue
Normal file
143
src/views/prison/area/AreaForm.vue
Normal file
@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="监区名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入监区名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="监区编码" prop="code">
|
||||
<el-input v-model="formData.code" placeholder="请输入监区编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="监区类型" prop="type">
|
||||
<el-select v-model="formData.type" placeholder="请选择监区类型">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_AREA_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="容纳人数" prop="capacity">
|
||||
<el-input v-model="formData.capacity" placeholder="请输入容纳人数" />
|
||||
</el-form-item>
|
||||
<el-form-item label="当前人数" prop="currentCount">
|
||||
<el-input v-model="formData.currentCount" placeholder="请输入当前人数" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input v-model="formData.sort" placeholder="请输入排序" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_CELL_STATUS)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" 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 { AreaApi, Area } from '@/api/prison/area'
|
||||
|
||||
/** 监区信息 表单 */
|
||||
defineOptions({ name: 'AreaForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
type: undefined,
|
||||
capacity: undefined,
|
||||
currentCount: undefined,
|
||||
sort: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '监区名称不能为空', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '监区编码不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
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 {
|
||||
formData.value = await AreaApi.getArea(id)
|
||||
} 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 as unknown as Area
|
||||
if (formType.value === 'create') {
|
||||
await AreaApi.createArea(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await AreaApi.updateArea(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
type: undefined,
|
||||
capacity: undefined,
|
||||
currentCount: undefined,
|
||||
sort: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
252
src/views/prison/area/index.vue
Normal file
252
src/views/prison/area/index.vue
Normal file
@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="监区名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
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-120px"
|
||||
>
|
||||
<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="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-90px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in statusOptions"
|
||||
: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:area:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:area:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
:disabled="checkedIds.length === 0"
|
||||
@click="handleDeleteBatch"
|
||||
v-hasPermi="['prison:area:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" 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="监区ID" align="center" prop="id" width="80" />
|
||||
<el-table-column label="监区名称" align="center" prop="name" width="120" />
|
||||
<el-table-column label="监区编码" align="center" prop="code" width="120" />
|
||||
<el-table-column label="监区类型" align="center" prop="type" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_AREA_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="容纳人数" align="center" prop="capacity" width="90" />
|
||||
<el-table-column label="当前人数" align="center" prop="currentCount" width="90" />
|
||||
<el-table-column label="排序" align="center" prop="sort" width="70" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_CELL_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['prison:area:update']"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['prison:area: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>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<AreaForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import download from '@/utils/download'
|
||||
import { AreaApi, Area } from '@/api/prison/area'
|
||||
import AreaForm from './AreaForm.vue'
|
||||
|
||||
defineOptions({ name: 'Area' })
|
||||
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(true)
|
||||
const list = ref<Area[]>([])
|
||||
const total = ref(0)
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
status: undefined
|
||||
})
|
||||
const queryFormRef = ref()
|
||||
const exportLoading = ref(false)
|
||||
|
||||
// 使用字典获取选项
|
||||
const typeOptions = getIntDictOptions(DICT_TYPE.PRISON_AREA_TYPE)
|
||||
const statusOptions = getIntDictOptions(DICT_TYPE.PRISON_CELL_STATUS)
|
||||
|
||||
/** 日期格式化 */
|
||||
const formatDate = (date: string | Date | undefined) => {
|
||||
if (!date) return '-'
|
||||
return new Date(date).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await AreaApi.getAreaPage(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 AreaApi.deleteArea(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除按钮操作 */
|
||||
const checkedIds = ref<number[]>([])
|
||||
const handleRowCheckboxChange = (rows: Area[]) => {
|
||||
checkedIds.value = rows.map((row) => row.id!)
|
||||
}
|
||||
|
||||
const handleDeleteBatch = async () => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await AreaApi.deleteAreaList(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 AreaApi.exportArea(queryParams)
|
||||
download.excel(data, '监区信息.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
137
src/views/prison/cell/CellForm.vue
Normal file
137
src/views/prison/cell/CellForm.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="所属监区ID" prop="areaId">
|
||||
<el-input v-model="formData.areaId" placeholder="请输入所属监区ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="监室名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入监室名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="监室编码" prop="code">
|
||||
<el-input v-model="formData.code" placeholder="请输入监室编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="床位数量" prop="capacity">
|
||||
<el-input v-model="formData.capacity" placeholder="请输入床位数量" />
|
||||
</el-form-item>
|
||||
<el-form-item label="当前人数" prop="currentCount">
|
||||
<el-input v-model="formData.currentCount" placeholder="请输入当前人数" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input v-model="formData.sort" placeholder="请输入排序" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_CELL_STATUS)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" 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 { CellApi, Cell } from '@/api/prison/cell'
|
||||
|
||||
/** 监室信息 表单 */
|
||||
defineOptions({ name: 'CellForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
areaId: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
capacity: undefined,
|
||||
currentCount: undefined,
|
||||
sort: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
areaId: [{ required: true, message: '所属监区ID不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '监室名称不能为空', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '监室编码不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
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 {
|
||||
formData.value = await CellApi.getCell(id)
|
||||
} 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 as unknown as Cell
|
||||
if (formType.value === 'create') {
|
||||
await CellApi.createCell(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await CellApi.updateCell(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
areaId: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
capacity: undefined,
|
||||
currentCount: undefined,
|
||||
sort: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
241
src/views/prison/cell/index.vue
Normal file
241
src/views/prison/cell/index.vue
Normal file
@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="所属监区" prop="areaId">
|
||||
<el-input
|
||||
v-model="queryParams.areaId"
|
||||
placeholder="请输入监区ID"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-140px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="监室名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入监室名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-160px"
|
||||
/>
|
||||
</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>
|
||||
<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:cell:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:cell:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
:disabled="checkedIds.length === 0"
|
||||
@click="handleDeleteBatch"
|
||||
v-hasPermi="['prison:cell:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" 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="监室ID" align="center" prop="id" width="80" />
|
||||
<el-table-column label="所属监区" align="center" prop="areaId" width="100" />
|
||||
<el-table-column label="监室名称" align="center" prop="name" width="120" />
|
||||
<el-table-column label="监室编码" align="center" prop="code" width="120" />
|
||||
<el-table-column label="床位数量" align="center" prop="capacity" width="90" />
|
||||
<el-table-column label="当前人数" align="center" prop="currentCount" width="90" />
|
||||
<el-table-column label="排序" align="center" prop="sort" width="70" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_CELL_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['prison:cell:update']"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['prison:cell: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>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<CellForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import download from '@/utils/download'
|
||||
import { CellApi, Cell } from '@/api/prison/cell'
|
||||
import CellForm from './CellForm.vue'
|
||||
|
||||
defineOptions({ name: 'Cell' })
|
||||
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(true)
|
||||
const list = ref<Cell[]>([])
|
||||
const total = ref(0)
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
areaId: undefined,
|
||||
name: undefined,
|
||||
status: undefined
|
||||
})
|
||||
const queryFormRef = ref()
|
||||
const exportLoading = ref(false)
|
||||
|
||||
// 使用字典获取选项
|
||||
const statusOptions = getIntDictOptions(DICT_TYPE.PRISON_CELL_STATUS)
|
||||
|
||||
/** 日期格式化 */
|
||||
const formatDate = (date: string | Date | undefined) => {
|
||||
if (!date) return '-'
|
||||
return new Date(date).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await CellApi.getCellPage(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 CellApi.deleteCell(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除按钮操作 */
|
||||
const checkedIds = ref<number[]>([])
|
||||
const handleRowCheckboxChange = (rows: Cell[]) => {
|
||||
checkedIds.value = rows.map((row) => row.id!)
|
||||
}
|
||||
|
||||
const handleDeleteBatch = async () => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await CellApi.deleteCellList(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 CellApi.exportCell(queryParams)
|
||||
download.excel(data, '监室信息.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
166
src/views/prison/consumption/ConsumptionForm.vue
Normal file
166
src/views/prison/consumption/ConsumptionForm.vue
Normal file
@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="罪犯ID" prop="prisonerId">
|
||||
<el-input v-model="formData.prisonerId" placeholder="请输入罪犯ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯编号" prop="prisonerNo">
|
||||
<el-input v-model="formData.prisonerNo" placeholder="请输入罪犯编号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-select v-model="formData.type" placeholder="请选择类型">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_CONSUMPTION_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="金额" prop="amount">
|
||||
<el-input v-model="formData.amount" placeholder="请输入金额" />
|
||||
</el-form-item>
|
||||
<el-form-item label="账户余额" prop="balance">
|
||||
<el-input v-model="formData.balance" placeholder="请输入账户余额" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品名称" prop="goodsName">
|
||||
<el-input v-model="formData.goodsName" placeholder="请输入商品名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品数量" prop="goodsCount">
|
||||
<el-input v-model="formData.goodsCount" placeholder="请输入商品数量" />
|
||||
</el-form-item>
|
||||
<el-form-item label="订单号" prop="orderNo">
|
||||
<el-input v-model="formData.orderNo" placeholder="请输入订单号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="交易时间" prop="tradeTime">
|
||||
<el-date-picker
|
||||
v-model="formData.tradeTime"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择交易时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_CONSUMPTION_STATUS)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" 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 { ConsumptionApi, Consumption } from '@/api/prison/consumption'
|
||||
|
||||
/** 消费记录 表单 */
|
||||
defineOptions({ name: 'ConsumptionForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
prisonerId: undefined,
|
||||
prisonerNo: undefined,
|
||||
type: undefined,
|
||||
amount: undefined,
|
||||
balance: undefined,
|
||||
goodsName: undefined,
|
||||
goodsCount: undefined,
|
||||
orderNo: undefined,
|
||||
tradeTime: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
prisonerId: [{ required: true, message: '罪犯ID不能为空', trigger: 'blur' }],
|
||||
prisonerNo: [{ required: true, message: '罪犯编号不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '类型不能为空', trigger: 'change' }],
|
||||
amount: [{ required: true, message: '金额不能为空', trigger: 'blur' }],
|
||||
tradeTime: [{ required: true, message: '交易时间不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
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 {
|
||||
formData.value = await ConsumptionApi.getConsumption(id)
|
||||
} 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 as unknown as Consumption
|
||||
if (formType.value === 'create') {
|
||||
await ConsumptionApi.createConsumption(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ConsumptionApi.updateConsumption(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
prisonerId: undefined,
|
||||
prisonerNo: undefined,
|
||||
type: undefined,
|
||||
amount: undefined,
|
||||
balance: undefined,
|
||||
goodsName: undefined,
|
||||
goodsCount: undefined,
|
||||
orderNo: undefined,
|
||||
tradeTime: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
258
src/views/prison/consumption/index.vue
Normal file
258
src/views/prison/consumption/index.vue
Normal file
@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="80px"
|
||||
>
|
||||
<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="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="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-90px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in statusOptions"
|
||||
: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:consumption:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:consumption:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
:disabled="checkedIds.length === 0"
|
||||
@click="handleDeleteBatch"
|
||||
v-hasPermi="['prison:consumption:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" 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="记录ID" align="center" prop="id" width="80" />
|
||||
<el-table-column label="罪犯编号" align="center" prop="prisonerNo" width="120" />
|
||||
<el-table-column label="类型" align="center" prop="type" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_CONSUMPTION_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" align="center" prop="amount" width="100" />
|
||||
<el-table-column label="账户余额" align="center" prop="balance" width="100" />
|
||||
<el-table-column label="商品名称" align="center" prop="goodsName" width="150" />
|
||||
<el-table-column label="商品数量" align="center" prop="goodsCount" width="90" />
|
||||
<el-table-column label="订单号" align="center" prop="orderNo" width="180" />
|
||||
<el-table-column label="交易时间" align="center" prop="tradeTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.tradeTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_CONSUMPTION_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['prison:consumption:update']"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['prison:consumption: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>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ConsumptionForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import download from '@/utils/download'
|
||||
import { ConsumptionApi, Consumption } from '@/api/prison/consumption'
|
||||
import ConsumptionForm from './ConsumptionForm.vue'
|
||||
|
||||
defineOptions({ name: 'Consumption' })
|
||||
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(true)
|
||||
const list = ref<Consumption[]>([])
|
||||
const total = ref(0)
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
prisonerNo: undefined,
|
||||
type: undefined,
|
||||
status: undefined
|
||||
})
|
||||
const queryFormRef = ref()
|
||||
const exportLoading = ref(false)
|
||||
|
||||
// 使用字典获取选项
|
||||
const typeOptions = getIntDictOptions(DICT_TYPE.PRISON_CONSUMPTION_TYPE)
|
||||
const statusOptions = getIntDictOptions(DICT_TYPE.PRISON_CONSUMPTION_STATUS)
|
||||
|
||||
/** 日期格式化 */
|
||||
const formatDate = (date: string | Date | undefined) => {
|
||||
if (!date) return '-'
|
||||
return new Date(date).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ConsumptionApi.getConsumptionPage(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 ConsumptionApi.deleteConsumption(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除按钮操作 */
|
||||
const checkedIds = ref<number[]>([])
|
||||
const handleRowCheckboxChange = (rows: Consumption[]) => {
|
||||
checkedIds.value = rows.map((row) => row.id!)
|
||||
}
|
||||
|
||||
const handleDeleteBatch = async () => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await ConsumptionApi.deleteConsumptionList(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 ConsumptionApi.exportConsumption(queryParams)
|
||||
download.excel(data, '消费记录.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
973
src/views/prison/question/QuestionForm.vue
Normal file
973
src/views/prison/question/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>
|
||||
247
src/views/prison/question/index.vue
Normal file
247
src/views/prison/question/index.vue
Normal file
@ -0,0 +1,247 @@
|
||||
<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">
|
||||
{{ formatDate(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)
|
||||
|
||||
// 问题类型枚举选项 (1-单选 2-多选 3-填空 4-评分)
|
||||
const questionTypeOptions = [
|
||||
{ label: '单选', value: 1 },
|
||||
{ label: '多选', value: 2 },
|
||||
{ label: '填空', value: 3 },
|
||||
{ label: '评分', value: 4 }
|
||||
]
|
||||
|
||||
/** 日期格式化 */
|
||||
const formatDate = (date: string | Date | undefined) => {
|
||||
if (!date) return '-'
|
||||
return new Date(date).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
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>
|
||||
134
src/views/prison/questionnaire/QuestionnaireForm.vue
Normal file
134
src/views/prison/questionnaire/QuestionnaireForm.vue
Normal file
@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="问卷标题" prop="title">
|
||||
<el-input v-model="formData.title" placeholder="请输入问卷标题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="问卷类型" prop="type">
|
||||
<el-select v-model="formData.type" placeholder="请选择问卷类型">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_QUESTIONNAIRE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="问卷说明" prop="description">
|
||||
<Editor v-model="formData.description" height="150px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="总分" prop="totalScore">
|
||||
<el-input v-model="formData.totalScore" placeholder="请输入总分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="及格分" prop="passScore">
|
||||
<el-input v-model="formData.passScore" placeholder="请输入及格分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="formData.status" placeholder="请选择状态">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_QUESTIONNAIRE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</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 { QuestionnaireApi, Questionnaire } from '@/api/prison/questionnaire'
|
||||
|
||||
/** 问卷模板 表单 */
|
||||
defineOptions({ name: 'QuestionnaireForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
title: undefined,
|
||||
type: undefined,
|
||||
description: undefined,
|
||||
totalScore: undefined,
|
||||
passScore: undefined,
|
||||
status: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
title: [{ required: true, message: '问卷标题不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '问卷类型不能为空', trigger: 'change' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
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 {
|
||||
formData.value = await QuestionnaireApi.getQuestionnaire(id)
|
||||
} 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 as unknown as Questionnaire
|
||||
if (formType.value === 'create') {
|
||||
await QuestionnaireApi.createQuestionnaire(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await QuestionnaireApi.updateQuestionnaire(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
title: undefined,
|
||||
type: undefined,
|
||||
description: undefined,
|
||||
totalScore: undefined,
|
||||
passScore: undefined,
|
||||
status: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
804
src/views/prison/questionnaire/components/QuestionList.vue
Normal file
804
src/views/prison/questionnaire/components/QuestionList.vue
Normal file
@ -0,0 +1,804 @@
|
||||
<template>
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<div class="question-header">
|
||||
<div class="header-left">
|
||||
<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="success"
|
||||
plain
|
||||
@click="openPartDialog"
|
||||
v-hasPermi="['prison:question:create']"
|
||||
>
|
||||
<Icon icon="ep:folder" 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>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<span class="total-count">共 {{ list.length }} 道问题</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分区显示(可拖拽排序) -->
|
||||
<div class="parts-container">
|
||||
<draggable
|
||||
v-model="partitions"
|
||||
item-key="name"
|
||||
handle=".part-drag-handle"
|
||||
:animation="200"
|
||||
@end="onPartitionDragEnd"
|
||||
class="parts-list"
|
||||
>
|
||||
<template #item="{ element: partition }">
|
||||
<div class="part-group" :class="{ 'is-default': !partition.name }">
|
||||
<!-- 分区头部 -->
|
||||
<div class="part-header">
|
||||
<div class="part-drag-handle">
|
||||
<Icon icon="ep:rank" class="drag-icon" />
|
||||
</div>
|
||||
<el-checkbox
|
||||
v-model="partition.selected"
|
||||
@change="(val: boolean) => togglePartQuestions(partition, val)"
|
||||
:disabled="!partition.name"
|
||||
>
|
||||
<span class="part-title">{{ partition.name || '默认分区' }}</span>
|
||||
</el-checkbox>
|
||||
<span class="part-count">({{ partition.questions.length }} 道题)</span>
|
||||
<div class="part-actions">
|
||||
<el-button
|
||||
v-if="partition.name"
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click="editPartition(partition)"
|
||||
>
|
||||
<Icon icon="ep:edit" /> 编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="partition.name"
|
||||
type="danger"
|
||||
link
|
||||
size="small"
|
||||
@click="deletePartition(partition)"
|
||||
>
|
||||
<Icon icon="ep:delete" /> 删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分区内的问题列表(可拖拽排序) -->
|
||||
<draggable
|
||||
v-model="partition.questions"
|
||||
item-key="id"
|
||||
handle=".question-drag-handle"
|
||||
:animation="200"
|
||||
@end="onQuestionDragEnd(partition)"
|
||||
class="questions-table"
|
||||
ghost-class="ghost-row"
|
||||
>
|
||||
<template #item="{ element: question }">
|
||||
<div class="question-row" :class="{ 'is-hidden': !partition.selected }">
|
||||
<div class="question-drag-handle">
|
||||
<Icon icon="ep:rank" class="drag-icon" />
|
||||
</div>
|
||||
<el-checkbox
|
||||
v-model="question._checked"
|
||||
@change="(val: boolean) => toggleQuestionCheck(question, val, partition)"
|
||||
class="question-checkbox"
|
||||
/>
|
||||
<span class="question-index">{{ question._index + 1 }}</span>
|
||||
<div class="question-info">
|
||||
<div class="question-title-row">
|
||||
<span class="type-badge" :class="'type-' + question.type">
|
||||
{{ getTypeLabel(question.type) }}
|
||||
</span>
|
||||
<span class="title-text">{{ question.title }}</span>
|
||||
<el-tag v-if="question.isRequired" type="danger" size="small">必填</el-tag>
|
||||
</div>
|
||||
<div v-if="question.helpText" class="help-text">
|
||||
<Icon icon="ep:info-filled" />
|
||||
{{ question.helpText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="question-meta">
|
||||
<span class="meta-item">
|
||||
<Icon icon="ep:coin" /> {{ question.score || 0 }}分
|
||||
</span>
|
||||
<el-tooltip
|
||||
v-if="question.autoFillType && question.autoFillType !== 'NONE'"
|
||||
:content="getAutoFillContent(question)"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag :type="question.autoFillType === 'AUTO' ? 'primary' : 'warning'" size="small">
|
||||
{{ getAutoFillLabel(question.autoFillType) }}
|
||||
</el-tag>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="question-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click="openForm('update', question.id)"
|
||||
v-hasPermi="['prison:question:update']"
|
||||
>
|
||||
<Icon icon="ep:edit" /> 修改
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
size="small"
|
||||
@click="handleDelete(question.id)"
|
||||
v-hasPermi="['prison:question:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" /> 删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<QuestionForm ref="formRef" @success="getList" />
|
||||
|
||||
<!-- 分区管理弹窗 -->
|
||||
<Dialog title="分区管理" v-model="partDialogVisible" width="600px">
|
||||
<el-form :model="partForm" label-width="100px">
|
||||
<el-form-item label="分区列表">
|
||||
<div class="part-manage-list">
|
||||
<div v-for="(part, index) in allPartitions" :key="part.id || index" class="part-manage-item">
|
||||
<el-icon class="drag-handle"><Rank /></el-icon>
|
||||
<el-input
|
||||
v-model="part.name"
|
||||
:placeholder="part.isDefault ? '默认分区' : '请输入分区名称'"
|
||||
:disabled="part.isDefault"
|
||||
style="flex: 1; margin: 0 10px"
|
||||
/>
|
||||
<el-input-number
|
||||
v-model="part.sort"
|
||||
:min="0"
|
||||
:max="999"
|
||||
controls-position="right"
|
||||
style="width: 100px"
|
||||
:disabled="part.isDefault"
|
||||
/>
|
||||
<el-button
|
||||
v-if="!part.isDefault"
|
||||
type="danger"
|
||||
:icon="Delete"
|
||||
circle
|
||||
@click="removePartition(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain :icon="Plus" @click="addPartition">添加分区</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="savePartitions" type="primary">保存设置</el-button>
|
||||
<el-button @click="partDialogVisible = false">取消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { QuestionApi, Question } from '@/api/prison/question'
|
||||
import QuestionForm from '../../question/QuestionForm.vue'
|
||||
import { Folder, InfoFilled, Rank, Plus, Delete } from '@element-plus/icons-vue'
|
||||
import draggable from 'vuedraggable'
|
||||
|
||||
defineOptions({ name: 'PrisonQuestionList' })
|
||||
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
questionnaireId?: number
|
||||
}>()
|
||||
|
||||
const loading = ref(false)
|
||||
const list = ref<Question[]>([])
|
||||
const formRef = ref()
|
||||
const partDialogVisible = ref(false)
|
||||
|
||||
// 分区列表(带问题)
|
||||
const partitions = ref<Array<{
|
||||
name: string
|
||||
sort: number
|
||||
selected: boolean
|
||||
questions: Array<Question & { _index: number; _checked: boolean }>
|
||||
}>>([])
|
||||
|
||||
// 所有分区(用于管理弹窗)
|
||||
const allPartList = ref<Array<{ id: string; name: string; sort: number; isDefault: boolean }>>([])
|
||||
const allPartitions = computed({
|
||||
get: () => allPartList.value,
|
||||
set: (val) => { allPartList.value = val }
|
||||
})
|
||||
|
||||
// 分区表单
|
||||
const partForm = ref({
|
||||
name: '',
|
||||
sort: 0
|
||||
})
|
||||
|
||||
// 选中的问题ID
|
||||
const checkedIds = ref<number[]>([])
|
||||
|
||||
/** 问题类型标签 */
|
||||
const getTypeLabel = (type: number) => {
|
||||
const labels: Record<number, string> = { 1: '单选', 2: '多选', 3: '填空', 4: '评分', 5: '日期', 6: '数字' }
|
||||
return labels[type] || '未知'
|
||||
}
|
||||
|
||||
/** 自动填充标签 */
|
||||
const getAutoFillLabel = (type: string) => {
|
||||
return { 'NONE': '-', 'AUTO': '自动', 'MANUAL': '手动' }[type] || type
|
||||
}
|
||||
|
||||
/** 自动填充内容 */
|
||||
const getAutoFillContent = (row: Question) => {
|
||||
if (row.autoFillType === 'AUTO') {
|
||||
return `自动填充来源:${row.autoFillSource || '-'}`
|
||||
}
|
||||
return '手动输入'
|
||||
}
|
||||
|
||||
/** 从问题列表提取所有分区 */
|
||||
const extractPartitions = (questions: Question[]) => {
|
||||
const partMap = new Map<string, Question[]>()
|
||||
const defaultPart: Question[] = []
|
||||
|
||||
questions.forEach(q => {
|
||||
const partName = q.partName || ''
|
||||
if (partName) {
|
||||
if (!partMap.has(partName)) {
|
||||
partMap.set(partName, [])
|
||||
}
|
||||
partMap.get(partName)!.push(q)
|
||||
} else {
|
||||
defaultPart.push(q)
|
||||
}
|
||||
})
|
||||
|
||||
// 按分区排序
|
||||
const sortedParts = Array.from(partMap.entries())
|
||||
.sort((a, b) => {
|
||||
const partA = partitions.value.find(p => p.name === a[0])
|
||||
const partB = partitions.value.find(p => p.name === b[0])
|
||||
const sortA = partA?.sort ?? 0
|
||||
const sortB = partB?.sort ?? 0
|
||||
return sortA - sortB
|
||||
})
|
||||
|
||||
// 构建分区列表
|
||||
const result: typeof partitions.value = []
|
||||
|
||||
// 添加默认分区
|
||||
result.push({
|
||||
name: '',
|
||||
sort: 0,
|
||||
selected: true,
|
||||
questions: defaultPart.map((q, i) => ({ ...q, _index: i, _checked: false }))
|
||||
})
|
||||
|
||||
// 添加其他分区
|
||||
sortedParts.forEach(([name, qs]) => {
|
||||
result.push({
|
||||
name,
|
||||
sort: partitions.value.find(p => p.name === name)?.sort ?? 0,
|
||||
selected: true,
|
||||
questions: qs.map((q, i) => ({ ...q, _index: i, _checked: false }))
|
||||
})
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/** 加载问题列表 */
|
||||
const getList = async () => {
|
||||
if (!props.questionnaireId) return
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await QuestionApi.getQuestionPage({
|
||||
pageNo: 1,
|
||||
pageSize: 200,
|
||||
questionnaireId: props.questionnaireId
|
||||
})
|
||||
list.value = data.list
|
||||
partitions.value = extractPartitions(data.list)
|
||||
|
||||
// 初始化分区管理列表(如果还没有初始化)
|
||||
if (allPartList.value.length === 0) {
|
||||
// 添加默认分区
|
||||
allPartList.value.push({
|
||||
id: 'default',
|
||||
name: '',
|
||||
sort: 0,
|
||||
isDefault: true
|
||||
})
|
||||
|
||||
// 添加已存在的分区
|
||||
const existingNames = new Set<string>()
|
||||
partitions.value.forEach(p => {
|
||||
if (p.name && !existingNames.has(p.name)) {
|
||||
existingNames.add(p.name)
|
||||
allPartList.value.push({
|
||||
id: `part_${p.name}`,
|
||||
name: p.name,
|
||||
sort: p.sort,
|
||||
isDefault: false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听问卷ID变化 */
|
||||
watch(
|
||||
() => props.questionnaireId,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
list.value = []
|
||||
partitions.value = []
|
||||
return
|
||||
}
|
||||
getList()
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
/** 打开表单 */
|
||||
const openForm = (type: string, id?: number) => {
|
||||
if (!props.questionnaireId) {
|
||||
message.error('请选择一个问卷')
|
||||
return
|
||||
}
|
||||
// 传入可用分区列表
|
||||
formRef.value.open(type, id, props.questionnaireId, getPartitionNames())
|
||||
}
|
||||
|
||||
/** 获取分区名称列表 */
|
||||
const getPartitionNames = () => {
|
||||
return allPartList.value
|
||||
.filter(p => !p.isDefault && p.name)
|
||||
.map((p, index) => ({
|
||||
label: p.name,
|
||||
value: p.name,
|
||||
sort: index
|
||||
}))
|
||||
.sort((a, b) => a.sort - b.sort)
|
||||
}
|
||||
|
||||
/** 打开分区管理弹窗 */
|
||||
const openPartDialog = () => {
|
||||
// 确保默认分区存在且在第一位
|
||||
if (allPartList.value.length === 0 || !allPartList.value[0]?.isDefault) {
|
||||
// 添加默认分区(第一个)
|
||||
allPartList.value.unshift({
|
||||
id: 'default',
|
||||
name: '',
|
||||
sort: 0,
|
||||
isDefault: true
|
||||
})
|
||||
}
|
||||
// 确保其他分区不是默认分区
|
||||
for (let i = 1; i < allPartList.value.length; i++) {
|
||||
allPartList.value[i].isDefault = false
|
||||
}
|
||||
partDialogVisible.value = true
|
||||
}
|
||||
|
||||
/** 添加分区 */
|
||||
const addPartition = () => {
|
||||
const newSort = allPartList.value.length > 0
|
||||
? Math.max(...allPartList.value.map(p => p.sort || 0)) + 1
|
||||
: 0
|
||||
allPartList.value.push({
|
||||
id: `part_${Date.now()}`,
|
||||
name: '',
|
||||
sort: newSort,
|
||||
isDefault: false
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除分区 */
|
||||
const removePartition = (index: number) => {
|
||||
allPartList.value.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 编辑分区名称 */
|
||||
const editPartition = (partition: typeof partitions.value[0]) => {
|
||||
const part = allPartList.value.find(p => p.name === partition.name)
|
||||
if (part) {
|
||||
// 找到输入框并激活
|
||||
message.info('请在下方分区管理列表中编辑分区名称')
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除分区 */
|
||||
const deletePartition = async (partition: typeof partitions.value[0]) => {
|
||||
try {
|
||||
await message.delConfirm(`确定删除分区"${partition.name}"吗?该分区下的问题将移到默认分区`)
|
||||
// 更新问题的分区为默认分区
|
||||
for (const q of partition.questions) {
|
||||
await QuestionApi.updateQuestion({ ...q, id: q.id, partName: '' })
|
||||
}
|
||||
await getList()
|
||||
message.success('删除成功')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 保存分区设置 */
|
||||
const savePartitions = async () => {
|
||||
try {
|
||||
// 验证分区名称唯一性(非默认分区)
|
||||
const names = allPartList.value.filter(p => !p.isDefault).map(p => p.name).filter(n => n)
|
||||
if (new Set(names).size !== names.length) {
|
||||
message.error('分区名称不能重复')
|
||||
return
|
||||
}
|
||||
|
||||
// 验证非默认分区必须有名称
|
||||
const emptyParts = allPartList.value.filter(p => !p.isDefault && !p.name)
|
||||
if (emptyParts.length > 0) {
|
||||
message.error('请为所有分区输入名称')
|
||||
return
|
||||
}
|
||||
|
||||
// 收集所有需要更新的问题
|
||||
const updates: Array<{ id: number; partName?: string; partSort?: number; sort?: number }> = []
|
||||
for (let i = 0; i < allPartList.value.length; i++) {
|
||||
const part = allPartList.value[i]
|
||||
if (!part.isDefault && part.name) {
|
||||
for (const p of partitions.value) {
|
||||
if (p.name === part.name) {
|
||||
p.questions.forEach((q, sortIndex) => {
|
||||
updates.push({
|
||||
id: q.id!,
|
||||
partName: part.name,
|
||||
partSort: i,
|
||||
sort: sortIndex
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量更新
|
||||
await QuestionApi.batchUpdate({ questions: updates })
|
||||
await getList()
|
||||
partDialogVisible.value = false
|
||||
message.success('保存成功')
|
||||
} catch (e) {
|
||||
message.error('保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
/** 分区拖拽排序完成 */
|
||||
const onPartitionDragEnd = async () => {
|
||||
// 收集所有需要更新的问题
|
||||
const updates: Array<{ id: number; partName?: string; partSort?: number; sort?: number }> = []
|
||||
for (let i = 0; i < partitions.value.length; i++) {
|
||||
const part = partitions.value[i]
|
||||
if (part.name) {
|
||||
part.questions.forEach((q, sortIndex) => {
|
||||
updates.push({
|
||||
id: q.id!,
|
||||
partName: part.name,
|
||||
partSort: i,
|
||||
sort: sortIndex
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
// 批量更新
|
||||
await QuestionApi.batchUpdate({ questions: updates })
|
||||
message.success('分区排序已更新')
|
||||
}
|
||||
|
||||
/** 问题拖拽排序完成 */
|
||||
const onQuestionDragEnd = async (partition: typeof partitions.value[0]) => {
|
||||
// 收集当前分区内需要更新的问题
|
||||
const updates: Array<{ id: number; partName?: string; partSort?: number; sort?: number }> = []
|
||||
partition.questions.forEach((q, i) => {
|
||||
updates.push({
|
||||
id: q.id!,
|
||||
partName: partition.name || undefined,
|
||||
partSort: partition.sort,
|
||||
sort: i
|
||||
})
|
||||
})
|
||||
// 批量更新
|
||||
await QuestionApi.batchUpdate({ questions: updates })
|
||||
message.success('问题排序已更新')
|
||||
}
|
||||
|
||||
/** 切换分区显示/隐藏 */
|
||||
const togglePartQuestions = (partition: typeof partitions.value[0], selected: boolean) => {
|
||||
partition.questions.forEach(q => {
|
||||
q._checked = selected
|
||||
})
|
||||
}
|
||||
|
||||
/** 切换问题选中状态 */
|
||||
const toggleQuestionCheck = (question: Question & { _index: number; _checked: boolean }, val: boolean, partition: typeof partitions.value[0]) => {
|
||||
if (val) {
|
||||
if (!checkedIds.value.includes(question.id!)) {
|
||||
checkedIds.value.push(question.id!)
|
||||
}
|
||||
} else {
|
||||
checkedIds.value = checkedIds.value.filter(id => id !== question.id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除问题 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await QuestionApi.deleteQuestion(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除 */
|
||||
const handleDeleteBatch = async () => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await QuestionApi.deleteQuestionList(checkedIds.value)
|
||||
checkedIds.value = []
|
||||
message.success(t('common.delSuccess'))
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.question-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.total-count {
|
||||
padding: 4px 12px;
|
||||
background: #f4f4f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.parts-container {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.parts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.part-group {
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.part-group:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.part-group.is-default {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.part-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: linear-gradient(135deg, #f0f7ff 0%, #e8f4fd 100%);
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.part-drag-handle {
|
||||
cursor: grab;
|
||||
color: #c0c4cc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.part-drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.drag-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.part-title {
|
||||
font-weight: 600;
|
||||
color: #1a5cb8;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.part-count {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.part-actions {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.questions-table {
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.question-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.question-row:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.question-row.is-hidden {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.question-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.ghost-row {
|
||||
background: #f0f9eb;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.question-drag-handle {
|
||||
cursor: grab;
|
||||
color: #c0c4cc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.question-drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.question-checkbox {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.question-index {
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.question-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.question-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.type-badge {
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.type-1 { background: #e6f7ff; color: #1890ff; }
|
||||
.type-2 { background: #f6ffed; color: #52c41a; }
|
||||
.type-3 { background: #fff7e6; color: #fa8c16; }
|
||||
.type-4 { background: #f9f0ff; color: #722ed1; }
|
||||
.type-5 { background: #fff1f0; color: #f5222d; }
|
||||
.type-6 { background: #e6fffb; color: #13c2c2; }
|
||||
|
||||
.help-text {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.question-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.question-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.part-manage-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.part-manage-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
margin-bottom: 8px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
cursor: move;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
</style>
|
||||
273
src/views/prison/questionnaire/index.vue
Normal file
273
src/views/prison/questionnaire/index.vue
Normal file
@ -0,0 +1,273 @@
|
||||
<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-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 questionnaireTypeOptions"
|
||||
: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-120px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in questionnaireStatusOptions"
|
||||
: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:questionnaire:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
:disabled="checkedIds.length === 0"
|
||||
@click="handleDeleteBatch"
|
||||
v-hasPermi="['prison:questionnaire:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:questionnaire:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
row-key="id"
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
highlight-current-row
|
||||
@current-change="handleCurrentChange"
|
||||
@selection-change="handleRowCheckboxChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="问卷ID" align="center" prop="id" width="80" />
|
||||
<el-table-column label="问卷标题" align="center" prop="title" width="200" />
|
||||
<el-table-column label="问卷类型" align="center" prop="type" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_QUESTIONNAIRE_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="问卷说明" align="center" prop="description" width="200" />
|
||||
<el-table-column label="总分" align="center" prop="totalScore" width="80" />
|
||||
<el-table-column label="及格分" align="center" prop="passScore" width="80" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_QUESTIONNAIRE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ formatDate(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:questionnaire:update']"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['prison:questionnaire: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>
|
||||
|
||||
<!-- 子表的列表 -->
|
||||
<ContentWrap>
|
||||
<el-tabs model-value="question">
|
||||
<el-tab-pane label="问卷问题" name="question">
|
||||
<QuestionList :questionnaire-id="currentRow.id" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<QuestionnaireForm 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 { QuestionnaireApi, Questionnaire } from '@/api/prison/questionnaire'
|
||||
import QuestionnaireForm from './QuestionnaireForm.vue'
|
||||
import QuestionList from './components/QuestionList.vue'
|
||||
|
||||
defineOptions({ name: 'Questionnaire' })
|
||||
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(true)
|
||||
const list = ref<Questionnaire[]>([])
|
||||
const total = ref(0)
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
title: undefined,
|
||||
type: undefined,
|
||||
status: undefined
|
||||
})
|
||||
const queryFormRef = ref()
|
||||
const exportLoading = ref(false)
|
||||
|
||||
// 使用字典获取选项
|
||||
const questionnaireTypeOptions = getIntDictOptions(DICT_TYPE.PRISON_QUESTIONNAIRE_TYPE)
|
||||
const questionnaireStatusOptions = getIntDictOptions(DICT_TYPE.PRISON_QUESTIONNAIRE_STATUS)
|
||||
|
||||
/** 日期格式化 */
|
||||
const formatDate = (date: string | Date | undefined) => {
|
||||
if (!date) return '-'
|
||||
return new Date(date).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await QuestionnaireApi.getQuestionnairePage(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 QuestionnaireApi.deleteQuestionnaire(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除按钮操作 */
|
||||
const checkedIds = ref<number[]>([])
|
||||
const handleRowCheckboxChange = (rows: Questionnaire[]) => {
|
||||
checkedIds.value = rows.map((row) => row.id!)
|
||||
}
|
||||
|
||||
const handleDeleteBatch = async () => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await QuestionnaireApi.deleteQuestionnaireList(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 QuestionnaireApi.exportQuestionnaire(queryParams)
|
||||
download.excel(data, '问卷模板.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 选中行操作 */
|
||||
const currentRow = ref<Questionnaire>({} as Questionnaire)
|
||||
const handleCurrentChange = (row: Questionnaire | undefined) => {
|
||||
currentRow.value = row || {} as Questionnaire
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
144
src/views/prison/questionnairerecord/QuestionnaireRecordForm.vue
Normal file
144
src/views/prison/questionnairerecord/QuestionnaireRecordForm.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="问卷ID" prop="questionnaireId">
|
||||
<el-input v-model="formData.questionnaireId" placeholder="请输入问卷ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯ID" prop="prisonerId">
|
||||
<el-input v-model="formData.prisonerId" placeholder="请输入罪犯ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯编号" prop="prisonerNo">
|
||||
<el-input v-model="formData.prisonerNo" placeholder="请输入罪犯编号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="得分" prop="totalScore">
|
||||
<el-input v-model="formData.totalScore" placeholder="请输入得分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否及格" prop="passStatus">
|
||||
<el-radio-group v-model="formData.passStatus">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_RECORD_PASS_STATUS)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="答题时间" prop="answerTime">
|
||||
<el-date-picker
|
||||
v-model="formData.answerTime"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择答题时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_RECORD_STATUS)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</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 { QuestionnaireRecordApi, QuestionnaireRecord } from '@/api/prison/questionnairerecord'
|
||||
|
||||
/** 问卷答题记录 表单 */
|
||||
defineOptions({ name: 'QuestionnaireRecordForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
questionnaireId: undefined,
|
||||
prisonerId: undefined,
|
||||
prisonerNo: undefined,
|
||||
totalScore: undefined,
|
||||
passStatus: undefined,
|
||||
answerTime: undefined,
|
||||
status: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
questionnaireId: [{ required: true, message: '问卷ID不能为空', trigger: 'blur' }],
|
||||
prisonerId: [{ required: true, message: '罪犯ID不能为空', trigger: 'blur' }],
|
||||
prisonerNo: [{ required: true, message: '罪犯编号不能为空', trigger: 'blur' }],
|
||||
answerTime: [{ required: true, message: '答题时间不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
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 {
|
||||
formData.value = await QuestionnaireRecordApi.getQuestionnaireRecord(id)
|
||||
} 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 as unknown as QuestionnaireRecord
|
||||
if (formType.value === 'create') {
|
||||
await QuestionnaireRecordApi.createQuestionnaireRecord(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await QuestionnaireRecordApi.updateQuestionnaireRecord(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
questionnaireId: undefined,
|
||||
prisonerId: undefined,
|
||||
prisonerNo: undefined,
|
||||
totalScore: undefined,
|
||||
passStatus: undefined,
|
||||
answerTime: undefined,
|
||||
status: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
321
src/views/prison/questionnairerecord/index.vue
Normal file
321
src/views/prison/questionnairerecord/index.vue
Normal file
@ -0,0 +1,321 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="问卷ID" prop="questionnaireId">
|
||||
<el-input
|
||||
v-model="queryParams.questionnaireId"
|
||||
placeholder="请输入问卷ID"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯ID" prop="prisonerId">
|
||||
<el-input
|
||||
v-model="queryParams.prisonerId"
|
||||
placeholder="请输入罪犯ID"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯编号" prop="prisonerNo">
|
||||
<el-input
|
||||
v-model="queryParams.prisonerNo"
|
||||
placeholder="请输入罪犯编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="得分" prop="totalScore">
|
||||
<el-input
|
||||
v-model="queryParams.totalScore"
|
||||
placeholder="请输入得分"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否及格" prop="passStatus">
|
||||
<el-select
|
||||
v-model="queryParams.passStatus"
|
||||
placeholder="请选择是否及格"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in passStatusOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="答题时间" prop="answerTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.answerTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-220px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in statusOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
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="openForm('create')"
|
||||
v-hasPermi="['prison:questionnaire-record:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:questionnaire-record:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
:disabled="isEmpty(checkedIds)"
|
||||
@click="handleDeleteBatch"
|
||||
v-hasPermi="['prison:questionnaire-record:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
row-key="id"
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
@selection-change="handleRowCheckboxChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="记录ID" align="center" prop="id" />
|
||||
<el-table-column label="问卷ID" align="center" prop="questionnaireId" />
|
||||
<el-table-column label="罪犯ID" align="center" prop="prisonerId" />
|
||||
<el-table-column label="罪犯编号" align="center" prop="prisonerNo" />
|
||||
<el-table-column label="得分" align="center" prop="totalScore" />
|
||||
<el-table-column label="是否及格" align="center" prop="passStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_RECORD_PASS_STATUS" :value="scope.row.passStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="答题时间"
|
||||
align="center"
|
||||
prop="answerTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_RECORD_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" min-width="120px">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['prison:questionnaire-record:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['prison:questionnaire-record: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>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<QuestionnaireRecordForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { QuestionnaireRecordApi, QuestionnaireRecord } from '@/api/prison/questionnairerecord'
|
||||
import QuestionnaireRecordForm from './QuestionnaireRecordForm.vue'
|
||||
|
||||
/** 问卷答题记录 列表 */
|
||||
defineOptions({ name: 'QuestionnaireRecord' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<QuestionnaireRecord[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
questionnaireId: undefined,
|
||||
prisonerId: undefined,
|
||||
prisonerNo: undefined,
|
||||
totalScore: undefined,
|
||||
passStatus: undefined,
|
||||
answerTime: [],
|
||||
status: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
// 使用字典获取选项
|
||||
const passStatusOptions = getIntDictOptions(DICT_TYPE.PRISON_RECORD_PASS_STATUS)
|
||||
const statusOptions = getIntDictOptions(DICT_TYPE.PRISON_RECORD_STATUS)
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await QuestionnaireRecordApi.getQuestionnaireRecordPage(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 QuestionnaireRecordApi.deleteQuestionnaireRecord(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除问卷答题记录 */
|
||||
const handleDeleteBatch = async () => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
await QuestionnaireRecordApi.deleteQuestionnaireRecordList(checkedIds.value);
|
||||
checkedIds.value = [];
|
||||
message.success(t('common.delSuccess'))
|
||||
await getList();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const checkedIds = ref<number[]>([])
|
||||
const handleRowCheckboxChange = (records: QuestionnaireRecord[]) => {
|
||||
checkedIds.value = records.map((item) => item.id!);
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await QuestionnaireRecordApi.exportQuestionnaireRecord(queryParams)
|
||||
download.excel(data, '问卷答题记录.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
203
src/views/prison/riskassessment/RiskAssessmentForm.vue
Normal file
203
src/views/prison/riskassessment/RiskAssessmentForm.vue
Normal file
@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="罪犯ID" prop="prisonerId">
|
||||
<el-input v-model="formData.prisonerId" placeholder="请输入罪犯ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯编号" prop="prisonerNo">
|
||||
<el-input v-model="formData.prisonerNo" placeholder="请输入罪犯编号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="评估类型" prop="assessmentType">
|
||||
<el-select v-model="formData.assessmentType" placeholder="请选择评估类型">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_ASSESSMENT_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="评估日期" prop="assessmentDate">
|
||||
<el-date-picker
|
||||
v-model="formData.assessmentDate"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择评估日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="暴力倾向得分" prop="violenceScore">
|
||||
<el-input v-model="formData.violenceScore" placeholder="请输入暴力倾向得分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="脱逃倾向得分" prop="escapeScore">
|
||||
<el-input v-model="formData.escapeScore" placeholder="请输入脱逃倾向得分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="自杀倾向得分" prop="suicideScore">
|
||||
<el-input v-model="formData.suicideScore" placeholder="请输入自杀倾向得分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="综合得分" prop="totalScore">
|
||||
<el-input v-model="formData.totalScore" placeholder="请输入综合得分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="风险等级" prop="riskLevel">
|
||||
<el-select v-model="formData.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="风险因素" prop="riskFactors">
|
||||
<el-input v-model="formData.riskFactors" placeholder="请输入风险因素" />
|
||||
</el-form-item>
|
||||
<el-form-item label="管控建议" prop="suggestions">
|
||||
<el-input v-model="formData.suggestions" placeholder="请输入管控建议" />
|
||||
</el-form-item>
|
||||
<el-form-item label="评估人ID" prop="assessorId">
|
||||
<el-input v-model="formData.assessorId" placeholder="请输入评估人ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="评估人姓名" prop="assessorName">
|
||||
<el-input v-model="formData.assessorName" placeholder="请输入评估人姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="下次评估日期" prop="nextAssessmentDate">
|
||||
<el-date-picker
|
||||
v-model="formData.nextAssessmentDate"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择下次评估日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_SCORE_STATUS)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" 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 { RiskAssessmentApi, RiskAssessment } from '@/api/prison/riskassessment'
|
||||
|
||||
/** 危险评估 表单 */
|
||||
defineOptions({ name: 'RiskAssessmentForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
prisonerId: undefined,
|
||||
prisonerNo: undefined,
|
||||
assessmentType: undefined,
|
||||
assessmentDate: undefined,
|
||||
violenceScore: undefined,
|
||||
escapeScore: undefined,
|
||||
suicideScore: undefined,
|
||||
totalScore: undefined,
|
||||
riskLevel: undefined,
|
||||
riskFactors: undefined,
|
||||
suggestions: undefined,
|
||||
assessorId: undefined,
|
||||
assessorName: undefined,
|
||||
nextAssessmentDate: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
prisonerId: [{ required: true, message: '罪犯ID不能为空', trigger: 'blur' }],
|
||||
prisonerNo: [{ required: true, message: '罪犯编号不能为空', trigger: 'blur' }],
|
||||
assessmentType: [{ required: true, message: '评估类型不能为空', trigger: 'change' }],
|
||||
assessmentDate: [{ required: true, message: '评估日期不能为空', trigger: 'blur' }],
|
||||
riskLevel: [{ required: true, message: '风险等级不能为空', trigger: 'change' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
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 {
|
||||
formData.value = await RiskAssessmentApi.getRiskAssessment(id)
|
||||
} 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 as unknown as RiskAssessment
|
||||
if (formType.value === 'create') {
|
||||
await RiskAssessmentApi.createRiskAssessment(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await RiskAssessmentApi.updateRiskAssessment(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
prisonerId: undefined,
|
||||
prisonerNo: undefined,
|
||||
assessmentType: undefined,
|
||||
assessmentDate: undefined,
|
||||
violenceScore: undefined,
|
||||
escapeScore: undefined,
|
||||
suicideScore: undefined,
|
||||
totalScore: undefined,
|
||||
riskLevel: undefined,
|
||||
riskFactors: undefined,
|
||||
suggestions: undefined,
|
||||
assessorId: undefined,
|
||||
assessorName: undefined,
|
||||
nextAssessmentDate: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
277
src/views/prison/riskassessment/index.vue
Normal file
277
src/views/prison/riskassessment/index.vue
Normal file
@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="80px"
|
||||
>
|
||||
<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="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="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-90px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in statusOptions"
|
||||
: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:risk-assessment:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:risk-assessment:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
:disabled="checkedIds.length === 0"
|
||||
@click="handleDeleteBatch"
|
||||
v-hasPermi="['prison:risk-assessment:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" 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="评估ID" align="center" prop="id" width="80" />
|
||||
<el-table-column label="罪犯编号" align="center" prop="prisonerNo" width="120" />
|
||||
<el-table-column label="评估类型" align="center" prop="assessmentType" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_ASSESSMENT_TYPE" :value="scope.row.assessmentType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="评估日期" align="center" prop="assessmentDate" width="120" />
|
||||
<el-table-column label="暴力得分" align="center" prop="violenceScore" 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="totalScore" width="90" />
|
||||
<el-table-column label="风险等级" align="center" prop="riskLevel" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_RISK_LEVEL" :value="scope.row.riskLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="风险因素" align="center" prop="riskFactors" width="150" />
|
||||
<el-table-column label="评估人" align="center" prop="assessorName" width="100" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_SCORE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['prison:risk-assessment:update']"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['prison:risk-assessment: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>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<RiskAssessmentForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import download from '@/utils/download'
|
||||
import { RiskAssessmentApi, RiskAssessment } from '@/api/prison/riskassessment'
|
||||
import RiskAssessmentForm from './RiskAssessmentForm.vue'
|
||||
|
||||
defineOptions({ name: 'RiskAssessment' })
|
||||
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(true)
|
||||
const list = ref<RiskAssessment[]>([])
|
||||
const total = ref(0)
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
prisonerNo: undefined,
|
||||
assessmentType: undefined,
|
||||
riskLevel: undefined,
|
||||
status: undefined
|
||||
})
|
||||
const queryFormRef = ref()
|
||||
const exportLoading = ref(false)
|
||||
|
||||
// 使用字典获取选项
|
||||
const assessmentTypeOptions = getIntDictOptions(DICT_TYPE.PRISON_ASSESSMENT_TYPE)
|
||||
const riskLevelOptions = getIntDictOptions(DICT_TYPE.PRISON_RISK_LEVEL)
|
||||
const statusOptions = getIntDictOptions(DICT_TYPE.PRISON_SCORE_STATUS)
|
||||
|
||||
/** 日期格式化 */
|
||||
const formatDate = (date: string | Date | undefined) => {
|
||||
if (!date) return '-'
|
||||
return new Date(date).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await RiskAssessmentApi.getRiskAssessmentPage(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 RiskAssessmentApi.deleteRiskAssessment(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除按钮操作 */
|
||||
const checkedIds = ref<number[]>([])
|
||||
const handleRowCheckboxChange = (rows: RiskAssessment[]) => {
|
||||
checkedIds.value = rows.map((row) => row.id!)
|
||||
}
|
||||
|
||||
const handleDeleteBatch = async () => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await RiskAssessmentApi.deleteRiskAssessmentList(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 RiskAssessmentApi.exportRiskAssessment(queryParams)
|
||||
download.excel(data, '危险评估.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
170
src/views/prison/score/ScoreForm.vue
Normal file
170
src/views/prison/score/ScoreForm.vue
Normal file
@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="罪犯ID" prop="prisonerId">
|
||||
<el-input v-model="formData.prisonerId" placeholder="请输入罪犯ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="罪犯编号" prop="prisonerNo">
|
||||
<el-input v-model="formData.prisonerNo" placeholder="请输入罪犯编号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="考核年份" prop="year">
|
||||
<el-input v-model="formData.year" placeholder="请输入考核年份" />
|
||||
</el-form-item>
|
||||
<el-form-item label="考核月份" prop="month">
|
||||
<el-input v-model="formData.month" placeholder="请输入考核月份" />
|
||||
</el-form-item>
|
||||
<el-form-item label="基础分" prop="baseScore">
|
||||
<el-input v-model="formData.baseScore" placeholder="请输入基础分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="加分" prop="rewardScore">
|
||||
<el-input v-model="formData.rewardScore" placeholder="请输入加分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="扣分" prop="penaltyScore">
|
||||
<el-input v-model="formData.penaltyScore" placeholder="请输入扣分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="总分" prop="totalScore">
|
||||
<el-input v-model="formData.totalScore" placeholder="请输入总分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="考核等级" prop="level">
|
||||
<el-select v-model="formData.level" placeholder="请选择考核等级">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_SCORE_LEVEL)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="考核人ID" prop="assessorId">
|
||||
<el-input v-model="formData.assessorId" placeholder="请输入考核人ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="考核人姓名" prop="assessorName">
|
||||
<el-input v-model="formData.assessorName" placeholder="请输入考核人姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_SCORE_STATUS)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" 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 { ScoreApi, Score } from '@/api/prison/score'
|
||||
|
||||
/** 计分考核 表单 */
|
||||
defineOptions({ name: 'ScoreForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
prisonerId: undefined,
|
||||
prisonerNo: undefined,
|
||||
year: undefined,
|
||||
month: undefined,
|
||||
baseScore: undefined,
|
||||
rewardScore: undefined,
|
||||
penaltyScore: undefined,
|
||||
totalScore: undefined,
|
||||
level: undefined,
|
||||
assessorId: undefined,
|
||||
assessorName: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
prisonerId: [{ required: true, message: '罪犯ID不能为空', trigger: 'blur' }],
|
||||
prisonerNo: [{ required: true, message: '罪犯编号不能为空', trigger: 'blur' }],
|
||||
year: [{ required: true, message: '考核年份不能为空', trigger: 'blur' }],
|
||||
month: [{ required: true, message: '考核月份不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
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 {
|
||||
formData.value = await ScoreApi.getScore(id)
|
||||
} 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 as unknown as Score
|
||||
if (formType.value === 'create') {
|
||||
await ScoreApi.createScore(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ScoreApi.updateScore(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
prisonerId: undefined,
|
||||
prisonerNo: undefined,
|
||||
year: undefined,
|
||||
month: undefined,
|
||||
baseScore: undefined,
|
||||
rewardScore: undefined,
|
||||
penaltyScore: undefined,
|
||||
totalScore: undefined,
|
||||
level: undefined,
|
||||
assessorId: undefined,
|
||||
assessorName: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
265
src/views/prison/score/index.vue
Normal file
265
src/views/prison/score/index.vue
Normal file
@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="80px"
|
||||
>
|
||||
<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="year">
|
||||
<el-input
|
||||
v-model="queryParams.year"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-80px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="考核等级" prop="level">
|
||||
<el-select
|
||||
v-model="queryParams.level"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-90px"
|
||||
>
|
||||
<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-90px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in statusOptions"
|
||||
: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:score:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:score:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
:disabled="checkedIds.length === 0"
|
||||
@click="handleDeleteBatch"
|
||||
v-hasPermi="['prison:score:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" 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="记录ID" align="center" prop="id" width="80" />
|
||||
<el-table-column label="罪犯编号" align="center" prop="prisonerNo" width="120" />
|
||||
<el-table-column label="年份" align="center" prop="year" width="80" />
|
||||
<el-table-column label="月份" align="center" prop="month" width="70" />
|
||||
<el-table-column label="基础分" align="center" prop="baseScore" width="80" />
|
||||
<el-table-column label="加分" align="center" prop="rewardScore" width="70" />
|
||||
<el-table-column label="扣分" align="center" prop="penaltyScore" width="70" />
|
||||
<el-table-column label="总分" align="center" prop="totalScore" width="80" />
|
||||
<el-table-column label="考核等级" align="center" prop="level" width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_SCORE_LEVEL" :value="scope.row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="考核人" align="center" prop="assessorName" width="100" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PRISON_SCORE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['prison:score:update']"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['prison:score: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>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ScoreForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import download from '@/utils/download'
|
||||
import { ScoreApi, Score } from '@/api/prison/score'
|
||||
import ScoreForm from './ScoreForm.vue'
|
||||
|
||||
defineOptions({ name: 'Score' })
|
||||
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(true)
|
||||
const list = ref<Score[]>([])
|
||||
const total = ref(0)
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
prisonerNo: undefined,
|
||||
year: undefined,
|
||||
level: undefined,
|
||||
status: undefined
|
||||
})
|
||||
const queryFormRef = ref()
|
||||
const exportLoading = ref(false)
|
||||
|
||||
// 使用字典获取选项
|
||||
const levelOptions = getIntDictOptions(DICT_TYPE.PRISON_SCORE_LEVEL)
|
||||
const statusOptions = getIntDictOptions(DICT_TYPE.PRISON_SCORE_STATUS)
|
||||
|
||||
/** 日期格式化 */
|
||||
const formatDate = (date: string | Date | undefined) => {
|
||||
if (!date) return '-'
|
||||
return new Date(date).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ScoreApi.getScorePage(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 ScoreApi.deleteScore(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 批量删除按钮操作 */
|
||||
const checkedIds = ref<number[]>([])
|
||||
const handleRowCheckboxChange = (rows: Score[]) => {
|
||||
checkedIds.value = rows.map((row) => row.id!)
|
||||
}
|
||||
|
||||
const handleDeleteBatch = async () => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
await ScoreApi.deleteScoreList(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 ScoreApi.exportScore(queryParams)
|
||||
download.excel(data, '计分考核.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
Loading…
x
Reference in New Issue
Block a user