tangweijie 5d43154ba5 feat: 新增问卷任务管理模块
- 新增问卷任务页面及组件(创建任务、人员选择、任务详情)
- 新增问卷预览组件
- 新增答题详情对话框
- 优化问卷列表和问卷记录页面
- 优化Dashboard风险趋势图Y轴动态缩放
- 更新评估报告导出页面
2026-01-24 10:56:02 +08:00

429 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="问卷" prop="questionnaireId">
<el-select
v-model="queryParams.questionnaireId"
placeholder="请选择问卷"
clearable
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
v-model="queryParams.prisonerNo"
placeholder="请输入罪犯编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</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="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="success"
plain
@click="handleInitiate"
v-hasPermi="['prison:questionnaire-record:initiate']"
>
<Icon icon="ep:promotion" class="mr-5px" /> 发起测评
</el-button>
<el-button
type="primary"
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" 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="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="duration" width="100">
<template #default="scope">
{{ scope.row.duration ? formatDuration(scope.row.duration) : '-' }}
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="160"
>
<template #default="scope">
{{ formatDateTime(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" min-width="280px" fixed="right">
<template #default="scope">
<el-button
v-if="scope.row.status === 3"
link
type="success"
@click="handleViewDetail(scope.row.id)"
v-hasPermi="['prison:questionnaire-record:query']"
>
查看详情
</el-button>
<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"
@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" />
<!-- 发起测评弹窗 -->
<InitiateAssessmentDialog ref="initiateDialogRef" @success="getList" />
<!-- 人工评分弹窗 -->
<ManualScoreDialog ref="manualScoreDialogRef" @success="getList" />
<!-- 答题详情弹窗 -->
<AnswerDetailDialog ref="answerDetailDialogRef" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { isEmpty } from '@/utils/is'
import { formatDateTime } from '@/utils/formatTime'
import download from '@/utils/download'
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'
import AnswerDetailDialog from './AnswerDetailDialog.vue'
/** 问卷答题记录/测评记录 列表 */
defineOptions({ name: 'QuestionnaireRecord' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<QuestionnaireRecord[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const questionnaireList = ref<any[]>([]) // 问卷列表
const queryParams = reactive<QuestionnaireRecordPageParams>({
pageNo: 1,
pageSize: 10,
questionnaireId: undefined,
prisonerNo: undefined,
status: undefined,
passStatus: undefined,
riskLevel: undefined
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
// 使用字典获取选项
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 () => {
loading.value = true
try {
const data = await QuestionnaireRecordApi.getQuestionnaireRecordPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 获取问卷列表 */
const getQuestionnaireList = async () => {
try {
const data = await QuestionnaireApi.getQuestionnairePage({ pageNo: 1, pageSize: 200 })
questionnaireList.value = data.list
} catch {}
}
/** 搜索按钮操作 */
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 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 answerDetailDialogRef = ref()
const handleViewDetail = (id: number) => {
answerDetailDialogRef.value.open(id)
}
/** 人工评分 */
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 = []
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
}
}
/** 格式化时长 */
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>