refactor: 前端重构评估模块为答题模块

主要变更:
- 删除 assessment 模块前端代码
- 消费记录模块表单和列表优化
- 问卷答题记录模块扩展测评执行和统计功能
- 更新字典配置

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
tangweijie 2026-01-15 22:36:24 +08:00
parent fdc6bf58e0
commit 2115e4aa52
15 changed files with 1056 additions and 331 deletions

View File

@ -0,0 +1,73 @@
import request from '@/config/axios'
/** 答题记录信息 */
export interface Answer {
id?: number // 记录ID
assessmentRecordId?: number // 测评记录ID
questionId?: number // 问题ID
questionnaireId?: number // 问卷ID
prisonerId?: number // 罪犯ID
questionType?: number // 问题类型1-单选 2-多选 3-填空 4-评分 5-日期 6-数字
answerText?: string // 答案内容
optionIds?: string // 选项ID列表
score?: number // 得分
isCorrect?: boolean // 是否正确
duration?: number // 答题用时(秒)
creator?: string // 创建者
createTime?: string // 创建时间
}
/** 答题记录分页参数 */
export interface AnswerPageParams {
pageNo: number
pageSize: number
assessmentRecordId?: number
questionId?: number
questionnaireId?: number
prisonerId?: number
questionType?: number
createTime?: string[]
}
// 答题记录 API
export const AnswerApi = {
// 查询答题记录分页
getAnswerPage: async (params: AnswerPageParams) => {
return await request.get({ url: `/prison/answer/page`, params })
},
// 查询答题记录详情
getAnswer: async (id: number) => {
return await request.get<Answer>({ url: `/prison/answer/get`, params: { id } })
},
// 新增答题记录
createAnswer: async (data: Answer) => {
return await request.post<number>({ url: `/prison/answer/create`, data })
},
// 修改答题记录
updateAnswer: async (data: Answer) => {
return await request.put<boolean>({ url: `/prison/answer/update`, data })
},
// 删除答题记录
deleteAnswer: async (id: number) => {
return await request.delete<boolean>({ url: `/prison/answer/delete`, params: { id } })
},
/** 批量删除答题记录 */
deleteAnswerList: async (ids: number[]) => {
return await request.post<boolean>({ url: `/prison/answer/delete-list`, data: ids })
},
// 根据测评记录ID查询答题列表
getAnswersByAssessmentRecordId: async (assessmentRecordId: number) => {
return await request.get<Answer[]>({ url: `/prison/answer/list-by-assessment-record`, params: { assessmentRecordId } })
},
// 导出答题记录 Excel
exportAnswer: async (params: AnswerPageParams) => {
return await request.download({ url: `/prison/answer/export-excel`, params })
}
}

View File

@ -1,56 +1,80 @@
import request from '@/config/axios'
import type { Dayjs } from 'dayjs';
/** 消费记录信息 */
/** 消费订单分页参数 */
export interface ConsumptionPageParams {
pageNo: number
pageSize: number
prisonerNo?: string
type?: number
status?: number
totalAmount?: number
orderNo?: string
}
/** 消费明细信息 */
export interface ConsumptionDetail {
id?: number // 明细ID
goodsName: string // 商品名称
goodsCode?: string // 商品编码
goodsPrice: number // 商品单价
goodsCount: number // 商品数量
subtotal?: number // 小计金额
}
/** 消费订单信息 */
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; // 备注
}
id: number // 订单ID
prisonerId?: number // 罪犯ID
prisonerNo?: string // 罪犯编号
orderNo?: string // 订单号
type?: number // 类型1-购物 2-餐饮 3-医疗 4-通讯 5-其他
totalAmount?: number // 订单总金额
balance: number // 账户余额
tradeTime?: string // 交易时间
status?: number // 状态1-成功 2-失败
remark: string // 备注
details?: ConsumptionDetail[] // 消费明细列表
}
// 消费记录 API
// 消费订单 API
export const ConsumptionApi = {
// 查询消费记录分页
getConsumptionPage: async (params: any) => {
// 查询消费订单分页
getConsumptionPage: async (params: ConsumptionPageParams) => {
return await request.get({ url: `/prison/consumption/page`, params })
},
// 查询消费记录详情
// 查询消费订单详情
getConsumption: async (id: number) => {
return await request.get({ url: `/prison/consumption/get?id=` + id })
return await request.get({ url: `/prison/consumption/get`, params: { 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 })
return await request.delete({ url: `/prison/consumption/delete`, params: { id } })
},
/** 批量删除消费记录 */
/** 批量删除消费订单 */
deleteConsumptionList: async (ids: number[]) => {
return await request.delete({ url: `/prison/consumption/delete-list?ids=${ids.join(',')}` })
},
// 导出消费记录 Excel
exportConsumption: async (params) => {
// 查询消费明细列表
getConsumptionDetailList: async (consumptionId: number) => {
return await request.get({ url: `/prison/consumption/detail-list`, params: { consumptionId } })
},
// 导出消费订单 Excel
exportConsumption: async (params: ConsumptionPageParams) => {
return await request.download({ url: `/prison/consumption/export-excel`, params })
}
}
}

View File

@ -1,15 +1,32 @@
import request from '@/config/axios'
/** 问卷答题记录信息 */
// ========== 问卷答题记录/测评记录类型 ==========
/** 问卷答题记录/测评记录信息 */
export interface QuestionnaireRecord {
id?: number // 记录ID创建时不需要
id?: number // 记录ID
questionnaireId?: number // 问卷ID
questionnaireName?: string // 问卷名称
prisonerId?: number // 罪犯ID
prisonerNo?: string // 罪犯编号
totalScore?: number // 得分
passStatus?: number // 是否及格0-未及格 1-及格
answerTime?: string // 答题时间
status?: number // 状态1-待评估 2-已完成
prisonerName?: string // 罪犯姓名
status?: number // 状态1-待测评 2-测评中 3-已完成 4-已过期 5-已取消
startTime?: string // 开始时间
endTime?: string // 结束时间
deadline?: string // 截止日期
objectiveScore?: number // 客观题得分
subjectiveScore?: number // 主观题得分
totalScore?: number // 总分
passScore?: number // 及格分数
passStatus?: number // 及格状态1-及格 2-不及格 3-待评阅
riskLevel?: number // 风险等级1-高风险 2-中风险 3-低风险
evaluatorId?: number // 评阅人ID
evaluatorName?: string // 评阅人姓名
evaluateTime?: string // 评阅时间
participantCount?: number // 参与人数
completedCount?: number // 完成人数
duration?: number // 答题用时(秒)
remark?: string // 备注
createTime?: string // 创建时间
}
@ -18,49 +35,156 @@ export interface QuestionnaireRecordPageParams {
pageNo: number
pageSize: number
questionnaireId?: number
questionnaireName?: string
prisonerId?: number
prisonerNo?: string
totalScore?: number
passStatus?: number
status?: number
answerTime?: string[]
passStatus?: number
riskLevel?: number
startTime?: string[]
endTime?: string[]
createTime?: string[]
}
// 问卷答题记录 API
/** 发起测评请求 */
export interface AssessmentInitiateReq {
questionnaireId: number // 问卷模板ID
prisonerIds: number[] // 罪犯ID列表
deadline?: string // 截止日期
remark?: string // 备注
}
/** 答题详情项 */
export interface AnswerItem {
questionId: number // 问题ID
answer?: string // 答案
optionIds?: number[] // 选择的选项ID列表
}
/** 提交答卷请求 */
export interface AssessmentAnswerSubmitReq {
recordId: number // 测评记录ID
prisonerId: number // 罪犯ID
answers: AnswerItem[] // 答题详情列表
}
/** 人工评分请求 */
export interface AssessmentManualScoreReq {
recordId: number // 测评记录ID
subjectiveScore: number // 主观题得分
comment?: string // 评语
riskLevel?: number // 风险等级1-高风险 2-中风险 3-低风险
}
/** 分数分布数据 */
export interface ScoreDistribution {
'0-20'?: number
'21-40'?: number
'41-60'?: number
'61-80'?: number
'81-100'?: number
}
/** 风险分布数据 */
export interface RiskDistribution {
high?: number
medium?: number
low?: number
}
// ========== API 对象 ==========
export const QuestionnaireRecordApi = {
// 查询问卷答题记录分页
// ========== 基础 CRUD ==========
/** 查询问卷答题记录分页 */
getQuestionnaireRecordPage: async (params: QuestionnaireRecordPageParams) => {
return await request.get({ url: `/prison/questionnaire-record/page`, params })
},
// 查询问卷答题记录详情
/** 查询问卷答题记录详情 */
getQuestionnaireRecord: async (id: number) => {
return await request.get<QuestionnaireRecord>({ url: `/prison/questionnaire-record/get`, params: { id } })
},
// 新增问卷答题记录
/** 新增问卷答题记录 */
createQuestionnaireRecord: async (data: QuestionnaireRecord) => {
return await request.post<number>({ url: `/prison/questionnaire-record/create`, data })
},
// 修改问卷答题记录
/** 修改问卷答题记录 */
updateQuestionnaireRecord: async (data: QuestionnaireRecord) => {
return await request.put<boolean>({ url: `/prison/questionnaire-record/update`, data })
},
// 删除问卷答题记录
/** 删除问卷答题记录 */
deleteQuestionnaireRecord: async (id: number) => {
return await request.delete<boolean>({ url: `/prison/questionnaire-record/delete`, params: { id } })
},
/** 批量删除问卷答题记录 */
deleteQuestionnaireRecordList: async (ids: number[]) => {
return await request.delete<boolean>({ url: `/prison/questionnaire-record/delete-list`, params: { ids: ids.join(',') } })
return await request.post<boolean>({ url: `/prison/questionnaire-record/delete-list`, data: ids })
},
// 导出问卷答题记录 Excel
/** 导出问卷答题记录 Excel */
exportQuestionnaireRecord: async (params: QuestionnaireRecordPageParams) => {
return await request.download({ url: `/prison/questionnaire-record/export-excel`, params })
},
// ========== 测评执行相关 ==========
/** 发起测评 */
initiateAssessment: async (data: AssessmentInitiateReq) => {
return await request.post<number>({ url: `/prison/questionnaire-record/initiate`, data })
},
/** 开始测评 */
startAssessment: async (id: number, prisonerId: number) => {
return await request.post<boolean>({ url: `/prison/questionnaire-record/start`, params: { id, prisonerId } })
},
/** 提交答卷 */
submitAnswer: async (data: AssessmentAnswerSubmitReq) => {
return await request.post<boolean>({ url: `/prison/questionnaire-record/submit`, data })
},
/** 结束测评 */
finishAssessment: async (id: number) => {
return await request.post<boolean>({ url: `/prison/questionnaire-record/finish`, params: { id } })
},
/** 取消测评 */
cancelAssessment: async (id: number) => {
return await request.post<boolean>({ url: `/prison/questionnaire-record/cancel`, params: { id } })
},
// ========== 评分相关 ==========
/** 自动评分 */
autoScore: async (id: number) => {
return await request.post<boolean>({ url: `/prison/questionnaire-record/auto-score`, params: { id } })
},
/** 人工评分 */
manualScore: async (data: AssessmentManualScoreReq) => {
return await request.post<boolean>({ url: `/prison/questionnaire-record/manual-score`, data })
},
// ========== 统计相关 ==========
/** 获取完成率 */
getCompletionRate: async (assessmentRecordId: number) => {
return await request.get<number>({ url: `/prison/questionnaire-record/completion-rate`, params: { assessmentRecordId } })
},
/** 获取分数分布 */
getScoreDistribution: async (assessmentRecordId: number) => {
return await request.get<ScoreDistribution>({ url: `/prison/questionnaire-record/score-distribution`, params: { assessmentRecordId } })
},
/** 获取风险分布 */
getRiskDistribution: async (assessmentRecordId: number) => {
return await request.get<RiskDistribution>({ url: `/prison/questionnaire-record/risk-distribution`, params: { assessmentRecordId } })
}
}

View File

@ -1,31 +1,40 @@
import request from '@/config/axios'
import type { Dayjs } from 'dayjs';
/** 危险评估分页参数 */
export interface RiskAssessmentPageParams {
pageNo: number
pageSize: number
prisonerNo?: string
assessmentType?: number
riskLevel?: number
status?: number
}
/** 危险评估信息 */
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; // 备注
}
id: number // 评估ID
prisonerId?: number // 罪犯ID
prisonerNo?: string // 罪犯编号
assessmentType?: number // 评估类型1-入狱评估 2-定期评估 3-专项评估
assessmentDate?: string // 评估日期
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 // 下次评估日期
status?: number // 状态1-待审核 2-已通过
remark: string // 备注
}
// 危险评估 API
export const RiskAssessmentApi = {
// 查询危险评估分页
getRiskAssessmentPage: async (params: any) => {
getRiskAssessmentPage: async (params: RiskAssessmentPageParams) => {
return await request.get({ url: `/prison/risk-assessment/page`, params })
},
@ -55,7 +64,7 @@ export const RiskAssessmentApi = {
},
// 导出危险评估 Excel
exportRiskAssessment: async (params) => {
exportRiskAssessment: async (params: RiskAssessmentPageParams) => {
return await request.download({ url: `/prison/risk-assessment/export-excel`, params })
}
}

View File

@ -1,28 +1,38 @@
import request from '@/config/axios'
import type { Dayjs } from 'dayjs';
/** 计分考核分页参数 */
export interface ScorePageParams {
pageNo: number
pageSize: number
prisonerNo?: string
year?: number
month?: number
level?: number
status?: number
}
/** 计分考核信息 */
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; // 备注
}
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) => {
getScorePage: async (params: ScorePageParams) => {
return await request.get({ url: `/prison/score/page`, params })
},
@ -52,7 +62,7 @@ export const ScoreApi = {
},
// 导出计分考核 Excel
exportScore: async (params) => {
exportScore: async (params: ScorePageParams) => {
return await request.download({ url: `/prison/score/export-excel`, params })
}
}

View File

@ -273,5 +273,8 @@ export enum DICT_TYPE {
PRISON_SCORE_CATEGORY = 'prison_score_category', // 考核类别1-劳动改造 2-教育改造 3-日常行为 4-卫生纪律 5-加分项 6-扣分项
PRISON_SCORE_TYPE = 'prison_score_type', // 考核类型1-加分 2-扣分
PRISON_RELEASE_STATUS = 'prison_release_status', // 释放状态1-待释放 2-已释放 3-已取消
PRISON_CERTIFICATE_TYPE = 'prison_certificate_type' // 证件类型1-身份证 2-户口簿 3-其他
PRISON_CERTIFICATE_TYPE = 'prison_certificate_type', // 证件类型1-身份证 2-户口簿 3-其他
PRISON_ASSESSMENT_STATUS = 'prison_assessment_status', // 测评状态1-待测评 2-测评中 3-已完成 4-已过期
PRISON_ASSESSMENT_PASS_STATUS = 'prison_assessment_pass_status', // 测评及格状态1-及格 2-不及格 3-待评分
PRISON_ASSESSMENT_ANSWER_STATUS = 'prison_assessment_answer_status' // 测评答题状态1-待评分 2-已评分
}

View File

@ -100,7 +100,7 @@
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
{{ formatDate(scope.row.createTime) }}
{{ dateFormatter(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="120">
@ -138,6 +138,7 @@
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { CellApi } from '@/api/prison/cell'
import { AreaApi } from '@/api/prison/area'
@ -168,7 +169,6 @@ const areaTreeData = ref<any[]>([])
// 使
const statusOptions = getIntDictOptions(DICT_TYPE.PRISON_CELL_STATUS)
/** 加载监区树形数据 */
const loadAreaTree = async () => {
try {
areaTreeData.value = await AreaApi.getAreaTree()
@ -177,12 +177,6 @@ const loadAreaTree = async () => {
}
}
/** 日期格式化 */
const formatDate = (date: string | Date | undefined) => {
if (!date) return '-'
return new Date(date).toLocaleString('zh-CN')
}
/** 查询列表 */
const getList = async () => {
loading.value = true

View File

@ -0,0 +1,47 @@
<template>
<Dialog title="消费明细" v-model="dialogVisible" width="600px">
<el-table :data="detailList" v-loading="loading">
<el-table-column label="商品名称" prop="goodsName" align="center" />
<el-table-column label="商品编码" prop="goodsCode" align="center" width="120" />
<el-table-column label="单价" prop="goodsPrice" align="center" width="100">
<template #default="{ row }">
¥{{ row.goodsPrice?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="数量" prop="goodsCount" align="center" width="80" />
<el-table-column label="小计" prop="subtotal" align="center" width="100">
<template #default="{ row }">
<span class="text-primary">¥{{ row.subtotal?.toFixed(2) }}</span>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="dialogVisible = false">关闭</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ConsumptionApi, ConsumptionDetail } from '@/api/prison/consumption'
defineOptions({ name: 'ConsumptionDetailDialog' })
const dialogVisible = ref(false)
const loading = ref(false)
const detailList = ref<ConsumptionDetail[]>([])
const consumptionId = ref<number>()
/** 打开弹窗 */
const open = async (id: number) => {
consumptionId.value = id
dialogVisible.value = true
loading.value = true
try {
detailList.value = await ConsumptionApi.getConsumptionDetailList(id)
} finally {
loading.value = false
}
}
defineExpose({ open })
</script>

View File

@ -1,5 +1,5 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<Dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
<el-form
ref="formRef"
:model="formData"
@ -7,57 +7,109 @@
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-row :gutter="20">
<el-col :span="8">
<el-form-item label="罪犯ID" prop="prisonerId">
<el-input v-model="formData.prisonerId" placeholder="请输入罪犯ID" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="罪犯编号" prop="prisonerNo">
<el-input v-model="formData.prisonerNo" placeholder="请输入罪犯编号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="类型" prop="type">
<el-select v-model="formData.type" placeholder="请选择类型" class="!w-full">
<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-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="订单总金额" prop="totalAmount">
<el-input-number v-model="formData.totalAmount" :precision="2" :step="0.01" :min="0" :max="99999999.99" placeholder="请输入金额" class="!w-full" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="账户余额" prop="balance">
<el-input-number v-model="formData.balance" :precision="2" :step="0.01" :min="0" :max="99999999.99" placeholder="请输入余额" class="!w-full" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="交易时间" prop="tradeTime">
<el-date-picker
v-model="formData.tradeTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择交易时间"
class="!w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<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-col>
<el-col :span="16">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
<!-- 消费明细列表 -->
<el-divider content-position="left">消费明细</el-divider>
<el-table :data="formData.details" border>
<el-table-column label="商品名称" prop="goodsName" width="150">
<template #default="{ row, $index }">
<el-input v-model="row.goodsName" placeholder="商品名称" />
</template>
</el-table-column>
<el-table-column label="商品编码" prop="goodsCode" width="120">
<template #default="{ row }">
<el-input v-model="row.goodsCode" placeholder="商品编码" />
</template>
</el-table-column>
<el-table-column label="单价" prop="goodsPrice" width="120">
<template #default="{ row }">
<el-input-number v-model="row.goodsPrice" :precision="2" :step="0.01" :min="0" controls-position="right" />
</template>
</el-table-column>
<el-table-column label="数量" prop="goodsCount" width="100">
<template #default="{ row }">
<el-input-number v-model="row.goodsCount" :min="1" controls-position="right" />
</template>
</el-table-column>
<el-table-column label="小计" prop="subtotal" width="100">
<template #default="{ row }">
{{ formatPrice((row.goodsPrice || 0) * (row.goodsCount || 0)) }}
</template>
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="{ row, $index }">
<el-button type="danger" link @click="removeDetail($index)" :disabled="formData.details.length <= 1">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-button type="primary" plain class="!w-full mt-10px" @click="addDetail">
<Icon icon="ep:plus" class="mr-5px" /> 添加商品
</el-button>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
@ -65,43 +117,65 @@
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { ConsumptionApi, Consumption } from '@/api/prison/consumption'
import { ConsumptionApi, Consumption, ConsumptionDetail } from '@/api/prison/consumption'
/** 消费记录 表单 */
/** 消费订单 表单 */
defineOptions({ name: 'ConsumptionForm' })
const { t } = useI18n() //
const message = useMessage() //
const { t } = useI18n()
const message = useMessage()
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formLoading = ref(false)
const formType = ref('')
const formData = ref({
id: undefined,
prisonerId: undefined,
prisonerNo: undefined,
type: undefined,
amount: undefined,
balance: undefined,
goodsName: undefined,
goodsCount: undefined,
orderNo: undefined,
type: undefined,
totalAmount: undefined,
balance: undefined,
tradeTime: undefined,
status: undefined,
remark: undefined
remark: undefined,
details: [] as ConsumptionDetail[]
})
const formRules = reactive({
const formRules = {
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' }],
totalAmount: [{ required: true, message: '订单总金额不能为空', trigger: 'blur' }],
tradeTime: [{ required: true, message: '交易时间不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
}
const formRef = ref()
/** 添加明细行 */
const addDetail = () => {
formData.value.details.push({
goodsName: '',
goodsCode: '',
goodsPrice: 0,
goodsCount: 1
})
}
/** 删除明细行 */
const removeDetail = (index: number) => {
formData.value.details.splice(index, 1)
}
/** 格式化价格 */
const formatPrice = (price: number) => {
return '¥' + price.toFixed(2)
}
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
@ -109,24 +183,30 @@ const open = async (type: string, id?: number) => {
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await ConsumptionApi.getConsumption(id)
const data = await ConsumptionApi.getConsumption(id)
formData.value = data
// details
if (!formData.value.details) {
formData.value.details = []
}
} finally {
formLoading.value = false
}
} else {
//
addDetail()
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
defineExpose({ open })
const emit = defineEmits(['success'])
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as Consumption
@ -138,29 +218,26 @@ const submitForm = async () => {
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,
type: undefined,
totalAmount: undefined,
balance: undefined,
tradeTime: undefined,
status: undefined,
remark: undefined
remark: undefined,
details: []
}
formRef.value?.resetFields()
}
</script>
</script>

View File

@ -25,7 +25,7 @@
class="!w-100px"
>
<el-option
v-for="dict in typeOptions"
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_CONSUMPTION_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@ -40,7 +40,7 @@
class="!w-90px"
>
<el-option
v-for="dict in statusOptions"
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_CONSUMPTION_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@ -88,21 +88,19 @@
@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="订单ID" align="center" prop="id" width="80" />
<el-table-column label="订单号" align="center" prop="orderNo" width="180" />
<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="totalAmount" 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) }}
{{ formatDateTime(scope.row.tradeTime) }}
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="90">
@ -112,10 +110,10 @@
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
{{ formatDate(scope.row.createTime) }}
{{ formatDateTime(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="120">
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<el-button
type="primary"
@ -125,6 +123,13 @@
>
修改
</el-button>
<el-button
type="primary"
link
@click="handleViewDetail(scope.row.id)"
>
明细
</el-button>
<el-button
type="danger"
link
@ -146,13 +151,18 @@
<!-- 表单弹窗添加/修改 -->
<ConsumptionForm ref="formRef" @success="getList" />
<!-- 明细查看弹窗 -->
<ConsumptionDetailDialog ref="detailDialogRef" />
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { formatDateTime } from '@/utils/formatTime'
import download from '@/utils/download'
import { ConsumptionApi, Consumption } from '@/api/prison/consumption'
import ConsumptionForm from './ConsumptionForm.vue'
import ConsumptionDetailDialog from './ConsumptionDetailDialog.vue'
defineOptions({ name: 'Consumption' })
@ -171,16 +181,8 @@ const queryParams = reactive({
})
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 checkedIds = ref<number[]>([])
const detailDialogRef = ref()
/** 查询列表 */
const getList = async () => {
@ -203,6 +205,9 @@ const handleQuery = () => {
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
//
queryParams.pageNo = 1
queryParams.pageSize = 10
handleQuery()
}
@ -212,6 +217,11 @@ const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 查看明细操作 */
const handleViewDetail = (id: number) => {
detailDialogRef.value.open(id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
@ -223,7 +233,6 @@ const handleDelete = async (id: number) => {
}
/** 批量删除按钮操作 */
const checkedIds = ref<number[]>([])
const handleRowCheckboxChange = (rows: Consumption[]) => {
checkedIds.value = rows.map((row) => row.id!)
}
@ -244,7 +253,7 @@ const handleExport = async () => {
await message.exportConfirm()
exportLoading.value = true
const data = await ConsumptionApi.exportConsumption(queryParams)
download.excel(data, '消费记录.xls')
download.excel(data, '消费订单.xls')
} catch {
} finally {
exportLoading.value = false

View File

@ -0,0 +1,167 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="title"
width="600px"
:close-on-click-modal="false"
>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="选择问卷" prop="questionnaireId">
<el-select
v-model="formData.questionnaireId"
placeholder="请选择问卷"
class="!w-100%"
filterable
>
<el-option
v-for="item in questionnaireList"
:key="item.id"
:label="item.title"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="选择罪犯" prop="prisonerIds">
<el-select
v-model="formData.prisonerIds"
placeholder="请选择罪犯"
class="!w-100%"
multiple
filterable
remote
:remote-method="searchPrisoners"
:loading="prisonerLoading"
>
<el-option
v-for="item in prisonerList"
:key="item.id"
:label="`${item.prisonerNo} - ${item.name}`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="截止日期" prop="deadline">
<el-date-picker
v-model="formData.deadline"
type="datetime"
placeholder="请选择截止日期"
class="!w-100%"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
确认发起
</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { QuestionnaireApi } from '@/api/prison/questionnaire'
import { QuestionnaireRecordApi } from '@/api/prison/questionnairerecord'
import { PrisonerApi, type PrisonerVO } from '@/api/prison/prisoner'
defineOptions({ name: 'InitiateAssessmentDialog' })
const emit = defineEmits(['success'])
const message = useMessage()
const dialogVisible = ref(false)
const title = ref('发起测评')
const formRef = ref()
const submitLoading = ref(false)
const questionnaireList = ref<any[]>([])
const prisonerList = ref<PrisonerVO[]>([])
const prisonerLoading = ref(false)
const formData = reactive({
questionnaireId: undefined as number | undefined,
prisonerIds: [] as number[],
deadline: '',
remark: ''
})
const rules = {
questionnaireId: [{ required: true, message: '请选择问卷', trigger: 'change' }],
prisonerIds: [{ required: true, message: '请选择罪犯', trigger: 'change' }]
}
/** 获取问卷列表 */
const getQuestionnaireList = async () => {
try {
const data = await QuestionnaireApi.getQuestionnairePage({ pageNo: 1, pageSize: 1000 })
questionnaireList.value = data.list
} catch {}
}
/** 搜索罪犯 */
const searchPrisoners = async (keyword: string) => {
if (!keyword) {
prisonerList.value = []
return
}
prisonerLoading.value = true
try {
const data = await PrisonerApi.getPrisonerPage({
pageNo: 1,
pageSize: 20,
prisonerNo: keyword
})
prisonerList.value = data.list
} catch {
prisonerList.value = []
} finally {
prisonerLoading.value = false
}
}
/** 打开弹窗 */
const open = () => {
dialogVisible.value = true
title.value = '发起测评'
//
formData.questionnaireId = undefined
formData.prisonerIds = []
formData.deadline = ''
formData.remark = ''
//
getQuestionnaireList()
//
prisonerList.value = []
}
/** 提交 */
const handleSubmit = async () => {
try {
await formRef.value.validate()
submitLoading.value = true
await QuestionnaireRecordApi.initiateAssessment({
questionnaireId: formData.questionnaireId!,
prisonerIds: formData.prisonerIds,
deadline: formData.deadline || undefined,
remark: formData.remark || undefined
})
message.success('测评发起成功')
dialogVisible.value = false
emit('success')
} catch {
// Validation error or API error
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>

View File

@ -0,0 +1,113 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="title"
width="500px"
:close-on-click-modal="false"
>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="罪犯信息">
<span>{{ currentRecord?.prisonerName || currentRecord?.prisonerNo }}</span>
</el-form-item>
<el-form-item label="客观分">
<span>{{ currentRecord?.objectiveScore || 0 }}</span>
</el-form-item>
<el-form-item label="主观题得分" prop="subjectiveScore">
<el-input-number
v-model="formData.subjectiveScore"
:min="0"
:max="100"
:precision="2"
class="!w-200px"
/>
</el-form-item>
<el-form-item label="风险等级" prop="riskLevel">
<el-select v-model="formData.riskLevel" placeholder="请选择风险等级" class="!w-200px">
<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="comment">
<el-input
v-model="formData.comment"
type="textarea"
:rows="3"
placeholder="请输入评语"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
确认评分
</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { QuestionnaireRecordApi, type QuestionnaireRecord } from '@/api/prison/questionnairerecord'
defineOptions({ name: 'ManualScoreDialog' })
const emit = defineEmits(['success'])
const message = useMessage()
const dialogVisible = ref(false)
const title = ref('人工评分')
const formRef = ref()
const submitLoading = ref(false)
const currentRecord = ref<QuestionnaireRecord | null>(null)
const riskLevelOptions = getIntDictOptions(DICT_TYPE.PRISON_RISK_LEVEL)
const formData = reactive({
subjectiveScore: 0,
riskLevel: undefined as number | undefined,
comment: ''
})
const rules = {
subjectiveScore: [{ required: true, message: '请输入主观题得分', trigger: 'blur' }]
}
/** 打开弹窗 */
const open = (record: QuestionnaireRecord) => {
dialogVisible.value = true
title.value = '人工评分'
currentRecord.value = record
//
formData.subjectiveScore = 0
formData.riskLevel = undefined
formData.comment = ''
}
/** 提交 */
const handleSubmit = async () => {
try {
await formRef.value.validate()
submitLoading.value = true
await QuestionnaireRecordApi.manualScore({
recordId: currentRecord.value.id!,
subjectiveScore: formData.subjectiveScore,
comment: formData.comment || undefined,
riskLevel: formData.riskLevel
})
message.success('评分成功')
dialogVisible.value = false
emit('success')
} catch {
// Validation error or API error
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>

View File

@ -8,23 +8,21 @@
:inline="true"
label-width="68px"
>
<el-form-item label="问卷ID" prop="questionnaireId">
<el-input
<el-form-item label="问卷" prop="questionnaireId">
<el-select
v-model="queryParams.questionnaireId"
placeholder="请输入问卷ID"
placeholder="请选择问卷"
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"
/>
filterable
>
<el-option
v-for="item in questionnaireList"
:key="item.id"
:label="item.title"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="罪犯编号" prop="prisonerNo">
<el-input
@ -35,42 +33,7 @@
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-form-item label="测评状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择状态"
@ -85,30 +48,49 @@
/>
</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 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="riskLevel">
<el-select
v-model="queryParams.riskLevel"
placeholder="请选择风险等级"
clearable
class="!w-240px"
>
<el-option
v-for="dict in riskLevelOptions"
: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"
type="success"
plain
@click="openForm('create')"
v-hasPermi="['prison:questionnaire-record:create']"
@click="handleInitiate"
v-hasPermi="['prison:questionnaire-record:initiate']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
<Icon icon="ep:promotion" class="mr-5px" /> 发起测评
</el-button>
<el-button
type="success"
type="primary"
plain
@click="handleExport"
:loading="exportLoading"
@ -117,11 +99,11 @@
<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']"
type="danger"
plain
:disabled="isEmpty(checkedIds)"
@click="handleDeleteBatch"
v-hasPermi="['prison:questionnaire-record:delete']"
>
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
</el-button>
@ -132,34 +114,39 @@
<!-- 列表 -->
<ContentWrap>
<el-table
row-key="id"
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
@selection-change="handleRowCheckboxChange"
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">
<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="questionnaireName" min-width="150" />
<el-table-column label="罪犯编号" align="center" prop="prisonerNo" width="120" />
<el-table-column label="罪犯姓名" align="center" prop="prisonerName" width="100" />
<el-table-column label="测评状态" align="center" prop="status" width="100">
<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="objectiveScore" width="80" />
<el-table-column label="主观分" align="center" prop="subjectiveScore" width="80" />
<el-table-column label="总分" align="center" prop="totalScore" width="80" />
<el-table-column label="及格状态" align="center" prop="passStatus" width="100">
<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">
<el-table-column label="风险等级" align="center" prop="riskLevel" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PRISON_RECORD_STATUS" :value="scope.row.status" />
<dict-tag :type="DICT_TYPE.PRISON_RISK_LEVEL" :value="scope.row.riskLevel" />
</template>
</el-table-column>
<el-table-column label="答题用时" align="center" prop="duration" width="100">
<template #default="scope">
{{ scope.row.duration ? formatDuration(scope.row.duration) : '-' }}
</template>
</el-table-column>
<el-table-column
@ -167,10 +154,46 @@
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
width="160"
/>
<el-table-column label="操作" align="center" min-width="120px">
<el-table-column label="操作" align="center" min-width="200px">
<template #default="scope">
<el-button
v-if="scope.row.status === 1"
link
type="primary"
@click="handleStart(scope.row)"
v-hasPermi="['prison:questionnaire-record:start']"
>
开始
</el-button>
<el-button
v-if="scope.row.status === 2"
link
type="success"
@click="handleFinish(scope.row)"
v-hasPermi="['prison:questionnaire-record:finish']"
>
结束
</el-button>
<el-button
v-if="scope.row.status === 1"
link
type="danger"
@click="handleCancel(scope.row)"
v-hasPermi="['prison:questionnaire-record:cancel']"
>
取消
</el-button>
<el-button
v-if="scope.row.status === 3 && scope.row.passStatus === 3"
link
type="warning"
@click="handleManualScore(scope.row)"
v-hasPermi="['prison:questionnaire-record:score']"
>
人工评分
</el-button>
<el-button
link
type="primary"
@ -201,6 +224,12 @@
<!-- 表单弹窗添加/修改 -->
<QuestionnaireRecordForm ref="formRef" @success="getList" />
<!-- 发起测评弹窗 -->
<InitiateAssessmentDialog ref="initiateDialogRef" @success="getList" />
<!-- 人工评分弹窗 -->
<ManualScoreDialog ref="manualScoreDialogRef" @success="getList" />
</template>
<script setup lang="ts">
@ -208,10 +237,13 @@ 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 { QuestionnaireRecordApi, QuestionnaireRecord, QuestionnaireRecordPageParams } from '@/api/prison/questionnairerecord'
import { QuestionnaireApi } from '@/api/prison/questionnaire'
import QuestionnaireRecordForm from './QuestionnaireRecordForm.vue'
import InitiateAssessmentDialog from './InitiateAssessmentDialog.vue'
import ManualScoreDialog from './ManualScoreDialog.vue'
/** 问卷答题记录 列表 */
/** 问卷答题记录/测评记录 列表 */
defineOptions({ name: 'QuestionnaireRecord' })
const message = useMessage() //
@ -220,24 +252,24 @@ const { t } = useI18n() // 国际化
const loading = ref(true) //
const list = ref<QuestionnaireRecord[]>([]) //
const total = ref(0) //
const queryParams = reactive({
const questionnaireList = ref<any[]>([]) //
const queryParams = reactive<QuestionnaireRecordPageParams>({
pageNo: 1,
pageSize: 10,
questionnaireId: undefined,
prisonerId: undefined,
prisonerNo: undefined,
totalScore: undefined,
passStatus: undefined,
answerTime: [],
status: undefined,
createTime: []
passStatus: undefined,
riskLevel: undefined
})
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 passStatusOptions = getIntDictOptions(DICT_TYPE.PRISON_RECORD_PASS_STATUS)
const riskLevelOptions = getIntDictOptions(DICT_TYPE.PRISON_RISK_LEVEL)
/** 查询列表 */
const getList = async () => {
@ -251,6 +283,14 @@ const getList = async () => {
}
}
/** 获取问卷列表 */
const getQuestionnaireList = async () => {
try {
const data = await QuestionnaireApi.getQuestionnairePage({ pageNo: 1, pageSize: 1000 })
questionnaireList.value = data.list
} catch {}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
@ -269,42 +309,78 @@ const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 发起测评 */
const initiateDialogRef = ref()
const handleInitiate = () => {
initiateDialogRef.value.open()
}
/** 开始测评 */
const handleStart = async (row: QuestionnaireRecord) => {
try {
await message.confirm(`确定要开始对罪犯 ${row.prisonerName || row.prisonerNo} 的测评吗?`)
await QuestionnaireRecordApi.startAssessment(row.id!, row.prisonerId!)
message.success('已开始测评')
getList()
} catch {}
}
/** 结束测评 */
const handleFinish = async (row: QuestionnaireRecord) => {
try {
await message.confirm(`确定要结束该测评吗?`)
await QuestionnaireRecordApi.finishAssessment(row.id!)
message.success('已结束测评')
getList()
} catch {}
}
/** 取消测评 */
const handleCancel = async (row: QuestionnaireRecord) => {
try {
await message.confirm(`确定要取消该测评吗?取消后将无法继续测评。`)
await QuestionnaireRecordApi.cancelAssessment(row.id!)
message.success('已取消测评')
getList()
} catch {}
}
/** 人工评分 */
const manualScoreDialogRef = ref()
const handleManualScore = (row: QuestionnaireRecord) => {
manualScoreDialogRef.value.open(row)
}
/** 删除按钮操作 */
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 = [];
await QuestionnaireRecordApi.deleteQuestionnaireRecordList(checkedIds.value)
checkedIds.value = []
message.success(t('common.delSuccess'))
await getList();
await getList()
} catch {}
}
const checkedIds = ref<number[]>([])
const handleRowCheckboxChange = (records: QuestionnaireRecord[]) => {
checkedIds.value = records.map((item) => item.id!);
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')
@ -314,8 +390,17 @@ const handleExport = async () => {
}
}
/** 格式化时长 */
const formatDuration = (seconds: number): string => {
if (!seconds) return '-'
const minutes = Math.floor(seconds / 60)
const secs = seconds % 60
return `${minutes}${secs}`
}
/** 初始化 **/
onMounted(() => {
getList()
getQuestionnaireList()
})
</script>
</script>

View File

@ -129,7 +129,7 @@
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
{{ formatDate(scope.row.createTime) }}
{{ dateFormatter(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="120">
@ -167,6 +167,7 @@
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { RiskAssessmentApi, RiskAssessment } from '@/api/prison/riskassessment'
import RiskAssessmentForm from './RiskAssessmentForm.vue'
@ -195,12 +196,6 @@ 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

View File

@ -118,7 +118,7 @@
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
{{ formatDate(scope.row.createTime) }}
{{ dateFormatter(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="120">
@ -156,6 +156,7 @@
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { ScoreApi, Score } from '@/api/prison/score'
import ScoreForm from './ScoreForm.vue'
@ -183,12 +184,6 @@ 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