feat: 问卷任务管理前端增强
- 新建问卷页面(question/index.vue) - 新增 Agent 填写对话框 - 优化任务详情对话框交互 - 新增 API 接口类型定义
This commit is contained in:
parent
5d43154ba5
commit
7272342fe6
@ -84,5 +84,10 @@ export const QuestionApi = {
|
||||
// 导出问卷问题 Excel
|
||||
exportQuestion: async (params: QuestionPageParams) => {
|
||||
return await request.download({ url: `/prison/question/export-excel`, params })
|
||||
},
|
||||
|
||||
// 获取问卷问题列表(不分页)
|
||||
getQuestionnaireQuestionList: async (params: QuestionPageParams) => {
|
||||
return await request.get({ url: `/prison/question/page`, params })
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,6 +90,40 @@ export interface TaskAreaStatistics {
|
||||
}
|
||||
}
|
||||
|
||||
/** 按监区统计 */
|
||||
export interface TaskAreaStatistics {
|
||||
areaId?: number
|
||||
areaName?: string
|
||||
totalCount: number
|
||||
completedCount: number
|
||||
completionRate: string | number
|
||||
avgScore: string | number
|
||||
passRate: string | number
|
||||
riskDistribution: {
|
||||
highRisk: number
|
||||
mediumRisk: number
|
||||
lowRisk: number
|
||||
}
|
||||
}
|
||||
|
||||
/** 人员填写进度 */
|
||||
export interface PrisonerProgress {
|
||||
id: number
|
||||
prisonerId: number
|
||||
prisonerNo: string
|
||||
prisonerName: string
|
||||
areaId?: number
|
||||
areaName?: string
|
||||
status: number
|
||||
objectiveScore?: number
|
||||
subjectiveScore?: number
|
||||
totalScore?: number
|
||||
riskLevel?: number
|
||||
duration?: number
|
||||
startTime?: string
|
||||
finishTime?: string
|
||||
}
|
||||
|
||||
/** 统计汇总 */
|
||||
export interface TaskStatisticsSummary {
|
||||
taskCount: number
|
||||
@ -170,6 +204,21 @@ export const QuestionnaireTaskApi = {
|
||||
return await request.post({ url: `/prison/questionnaire-task/remind`, params: { id } })
|
||||
},
|
||||
|
||||
// 获取任务的人员填写进度列表
|
||||
getPrisonerProgress: async (id: number) => {
|
||||
return await request.get<PrisonerProgress[]>({ url: `/prison/questionnaire-task/prisoner-progress`, params: { id } })
|
||||
},
|
||||
|
||||
// 通知单个人员
|
||||
notifyPrisoner: async (recordId: number) => {
|
||||
return await request.post({ url: `/prison/questionnaire-task/notify-prisoner`, params: { recordId } })
|
||||
},
|
||||
|
||||
// 重置人员答题记录
|
||||
resetPrisonerRecord: async (recordId: number) => {
|
||||
return await request.post({ url: `/prison/questionnaire-task/reset-record`, params: { recordId } })
|
||||
},
|
||||
|
||||
// ==================== 统计相关 ====================
|
||||
|
||||
// 按监区统计任务完成情况
|
||||
|
||||
@ -160,6 +160,11 @@ export const QuestionnaireRecordApi = {
|
||||
return await request.post<boolean>({ url: `/prison/questionnaire-record/submit`, data })
|
||||
},
|
||||
|
||||
/** 代为提交答卷(民警代填) */
|
||||
submitAnswerByAgent: async (data: AssessmentAnswerSubmitReq) => {
|
||||
return await request.post<boolean>({ url: `/prison/questionnaire-record/submit-by-agent`, data })
|
||||
},
|
||||
|
||||
/** 结束测评 */
|
||||
finishAssessment: async (id: number) => {
|
||||
return await request.post<boolean>({ url: `/prison/questionnaire-record/finish`, params: { id } })
|
||||
|
||||
9
src/views/prison/question/index.vue
Normal file
9
src/views/prison/question/index.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<el-empty description="问卷题目管理功能已整合到问卷管理中" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'PrisonQuestion' })
|
||||
</script>
|
||||
@ -0,0 +1,331 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="`代为填写 - ${prisonerInfo?.prisonerName || ''}`"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-loading="loading">
|
||||
<!-- 罪犯信息 -->
|
||||
<el-descriptions :column="3" border class="mb-20px">
|
||||
<el-descriptions-item label="罪犯编号">{{ prisonerInfo?.prisonerNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="罪犯姓名">{{ prisonerInfo?.prisonerName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="监区">{{ prisonerInfo?.areaName }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 问卷题目 -->
|
||||
<div v-if="questions.length > 0" class="questionnaire-content">
|
||||
<div
|
||||
v-for="(question, index) in questions"
|
||||
:key="question.id"
|
||||
class="question-item"
|
||||
>
|
||||
<div class="question-header">
|
||||
<span class="question-index">{{ index + 1 }}</span>
|
||||
<span class="question-title">{{ question.title }}</span>
|
||||
<el-tag v-if="question.required" type="danger" size="small" class="required-tag">必填</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 单选题 -->
|
||||
<div v-if="question.type === 1" class="answer-area">
|
||||
<div class="options-container">
|
||||
<el-radio-group v-model="answers[question.id]" :disabled="disabled">
|
||||
<el-radio
|
||||
v-for="(opt, index) in parseOptions(question.options)"
|
||||
:key="index"
|
||||
:value="index"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 多选题 -->
|
||||
<div v-else-if="question.type === 2" class="answer-area">
|
||||
<div class="options-container">
|
||||
<el-checkbox-group v-model="multiAnswers[question.id]">
|
||||
<el-checkbox
|
||||
v-for="(opt, index) in parseOptions(question.options)"
|
||||
:key="index"
|
||||
:value="index"
|
||||
:disabled="disabled"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 填空题 -->
|
||||
<div v-else-if="question.type === 3" class="answer-area">
|
||||
<el-input
|
||||
v-model="answers[question.id]"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入答案"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 评分题 -->
|
||||
<div v-else-if="question.type === 4" class="answer-area">
|
||||
<el-rate v-model="answers[question.id]" :disabled="disabled" />
|
||||
</div>
|
||||
|
||||
<!-- 日期题 -->
|
||||
<div v-else-if="question.type === 5" class="answer-area">
|
||||
<el-date-picker
|
||||
v-model="answers[question.id]"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 数字题 -->
|
||||
<div v-else-if="question.type === 6" class="answer-area">
|
||||
<el-input-number v-model="answers[question.id]" :min="0" :disabled="disabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty v-else description="暂无问卷题目" />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">
|
||||
确认代填
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { QuestionnaireApi } from '@/api/prison/questionnaire'
|
||||
import { QuestionApi } from '@/api/prison/question'
|
||||
import { QuestionnaireRecordApi } from '@/api/prison/questionnairerecord'
|
||||
|
||||
defineOptions({ name: 'AgentFillDialog' })
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 罪犯信息
|
||||
const prisonerInfo = ref<any>(null)
|
||||
const recordId = ref<number>()
|
||||
const questionnaireId = ref<number>()
|
||||
|
||||
// 问卷题目
|
||||
const questions = ref<any[]>([])
|
||||
|
||||
// 答案
|
||||
const answers = reactive<Record<number, any>>({})
|
||||
const multiAnswers = reactive<Record<number, number[]>>({})
|
||||
|
||||
// 是否禁用
|
||||
const disabled = computed(() => submitLoading.value)
|
||||
|
||||
/** 解析选项JSON */
|
||||
const parseOptions = (optionsStr: string) => {
|
||||
try {
|
||||
if (!optionsStr) return []
|
||||
return JSON.parse(optionsStr)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (record: any) => {
|
||||
dialogVisible.value = true
|
||||
recordId.value = record.id
|
||||
prisonerInfo.value = record
|
||||
questionnaireId.value = record.questionnaireId
|
||||
|
||||
// 重置答案
|
||||
Object.keys(answers).forEach(key => delete answers[key])
|
||||
Object.keys(multiAnswers).forEach(key => delete multiAnswers[key])
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
// 先获取问卷题目总数
|
||||
const countData = await QuestionApi.getQuestionnaireQuestionList(
|
||||
{ questionnaireId: questionnaireId.value, pageNo: 1, pageSize: 1 }
|
||||
)
|
||||
const totalCount = countData.total || 0
|
||||
|
||||
// 根据总数获取所有题目(后端限制每页最大200条)
|
||||
const maxPageSize = 200
|
||||
const pageSize = Math.min(totalCount, maxPageSize)
|
||||
|
||||
const questionData = await QuestionApi.getQuestionnaireQuestionList(
|
||||
{ questionnaireId: questionnaireId.value, pageNo: 1, pageSize: pageSize }
|
||||
)
|
||||
questions.value = questionData.list || []
|
||||
|
||||
// 初始化答案结构
|
||||
questions.value.forEach(q => {
|
||||
if (q.type === 2) {
|
||||
// 多选题初始化为空数组
|
||||
multiAnswers[q.id] = []
|
||||
} else {
|
||||
answers[q.id] = q.type === 4 ? 0 : null // 评分题默认0分
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('获取问卷题目失败', e)
|
||||
ElMessage.error('获取问卷题目失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交答案 */
|
||||
const handleSubmit = async () => {
|
||||
// 校验必填
|
||||
for (const question of questions.value) {
|
||||
if (question.required) {
|
||||
const answer = question.type === 2 ? multiAnswers[question.id] : answers[question.id]
|
||||
if (answer === null || answer === undefined ||
|
||||
(Array.isArray(answer) && answer.length === 0) ||
|
||||
(typeof answer === 'string' && !answer.trim())) {
|
||||
ElMessage.warning(`第${questions.value.indexOf(question) + 1}题为必填项`)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构建答案列表
|
||||
const answerList = questions.value.map(q => {
|
||||
const answerItem: any = {
|
||||
questionId: q.id
|
||||
}
|
||||
|
||||
if (q.type === 2) {
|
||||
// 多选题
|
||||
answerItem.optionIds = multiAnswers[q.id] || []
|
||||
} else {
|
||||
answerItem.answer = answers[q.id] !== undefined ? String(answers[q.id]) : ''
|
||||
}
|
||||
|
||||
return answerItem
|
||||
})
|
||||
|
||||
// 根据记录状态决定是否需要先开始测评
|
||||
const needStartAssessment = prisonerInfo.value?.status === 1 // 只有"待测评"状态需要先开始测评
|
||||
|
||||
if (needStartAssessment) {
|
||||
try {
|
||||
await QuestionnaireRecordApi.startAssessment(recordId.value!, prisonerInfo.value.prisonerId)
|
||||
} catch (e) {
|
||||
console.error('开始测评失败', e)
|
||||
ElMessage.error('开始测评失败')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 提交答案
|
||||
submitLoading.value = true
|
||||
try {
|
||||
await QuestionnaireRecordApi.submitAnswerByAgent({
|
||||
recordId: recordId.value!,
|
||||
prisonerId: prisonerInfo.value.prisonerId,
|
||||
answers: answerList
|
||||
})
|
||||
ElMessage.success('代填成功')
|
||||
dialogVisible.value = false
|
||||
// 触发成功回调
|
||||
emit('success')
|
||||
} catch (e) {
|
||||
console.error('提交失败', e)
|
||||
ElMessage.error('提交失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.questionnaire-content {
|
||||
overflow-y: auto;
|
||||
max-height: 55vh;
|
||||
padding-right: 8px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #e4e7ed;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.question-item {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.question-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.question-index {
|
||||
flex-shrink: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.question-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.answer-area {
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-radio),
|
||||
:deep(.el-checkbox) {
|
||||
margin-right: 0;
|
||||
white-space: normal;
|
||||
height: auto;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
:deep(.el-radio__label),
|
||||
:deep(.el-checkbox__label) {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -23,7 +23,7 @@
|
||||
v-model="formData.questionnaireId"
|
||||
placeholder="请选择问卷"
|
||||
filterable
|
||||
style="width: 100%"
|
||||
style="width: calc(100% - 90px)"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in questionnaireList"
|
||||
@ -32,6 +32,14 @@
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
:disabled="!formData.questionnaireId"
|
||||
@click="handlePreviewQuestionnaire"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-3px" />预览
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 目标范围 -->
|
||||
@ -115,6 +123,19 @@
|
||||
@confirm="handlePrisonerSelect"
|
||||
/>
|
||||
|
||||
<!-- 问卷预览弹窗 -->
|
||||
<el-dialog
|
||||
v-model="previewVisible"
|
||||
title="问卷预览"
|
||||
width="800px"
|
||||
destroy-on-close
|
||||
>
|
||||
<QuestionnairePreview :id="previewQuestionnaireId" />
|
||||
<template #footer>
|
||||
<el-button @click="previewVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
@ -128,6 +149,7 @@
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import PrisonerSelectorDialog from './PrisonerSelectorDialog.vue'
|
||||
import QuestionnairePreview from '@/views/prison/questionnaire/components/QuestionnairePreview.vue'
|
||||
import { QuestionnaireTaskApi } from '@/api/prison/questionnaire-task'
|
||||
import { QuestionnaireApi } from '@/api/prison/questionnaire'
|
||||
import { AreaApi } from '@/api/prison/area'
|
||||
@ -141,6 +163,10 @@ const submitLoading = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const formRef = ref()
|
||||
|
||||
// 预览相关
|
||||
const previewVisible = ref(false)
|
||||
const previewQuestionnaireId = ref<number>()
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
id: undefined,
|
||||
@ -239,6 +265,16 @@ const openPrisonerSelector = () => {
|
||||
prisonerSelectorRef.value?.open(formData.prisonerIds)
|
||||
}
|
||||
|
||||
/** 预览问卷 */
|
||||
const handlePreviewQuestionnaire = () => {
|
||||
if (!formData.questionnaireId) {
|
||||
ElMessage.warning('请先选择问卷')
|
||||
return
|
||||
}
|
||||
previewQuestionnaireId.value = formData.questionnaireId
|
||||
previewVisible.value = true
|
||||
}
|
||||
|
||||
/** 犯人选择确认 */
|
||||
const handlePrisonerSelect = (selectedIds: number[]) => {
|
||||
formData.prisonerIds = selectedIds
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="taskDetail?.taskName || '任务详情'"
|
||||
width="900px"
|
||||
width="1100px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div v-loading="loading">
|
||||
@ -20,144 +20,322 @@
|
||||
<el-descriptions-item label="截止时间">{{ formatDateTime(taskDetail?.deadline) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 完成进度 -->
|
||||
<el-card class="mb-20px">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>完成进度</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<div class="progress-item">
|
||||
<div class="progress-value">{{ taskProgress?.totalCount || 0 }}</div>
|
||||
<div class="progress-label">目标人数</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="progress-item">
|
||||
<div class="progress-value text-success">{{ taskProgress?.completedCount || 0 }}</div>
|
||||
<div class="progress-label">已完成</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="progress-item">
|
||||
<div class="progress-value text-warning">{{ taskProgress?.pendingCount || 0 }}</div>
|
||||
<div class="progress-label">待完成</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="progress-item">
|
||||
<div class="progress-value text-primary">{{ taskProgress?.completionRate }}%</div>
|
||||
<div class="progress-label">完成率</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="mt-20px">
|
||||
<el-progress
|
||||
:percentage="Number(taskProgress?.completionRate) || 0"
|
||||
:stroke-width="20"
|
||||
status="success"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 状态分布 -->
|
||||
<el-row :gutter="20" class="mt-20px">
|
||||
<el-col :span="8">
|
||||
<el-statistic title="待测评" :value="taskProgress?.statusBreakdown?.pending || 0">
|
||||
<template #suffix>
|
||||
<span class="text-gray">人</span>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-statistic title="测评中" :value="taskProgress?.statusBreakdown?.inProgress || 0">
|
||||
<template #suffix>
|
||||
<span class="text-gray">人</span>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-statistic title="已完成" :value="taskProgress?.statusBreakdown?.completed || 0">
|
||||
<template #suffix>
|
||||
<span class="text-gray">人</span>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<!-- 按监区统计 -->
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>按监区统计</span>
|
||||
<el-button
|
||||
v-if="taskProgress?.pendingCount > 0"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleRemind"
|
||||
:loading="remindLoading"
|
||||
>
|
||||
提醒未完成
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table :data="areaStatistics" stripe>
|
||||
<el-table-column label="监区" align="center" prop="areaName" width="120" />
|
||||
<el-table-column label="目标人数" align="center" prop="totalCount" width="100" />
|
||||
<el-table-column label="已完成" align="center" prop="completedCount" width="100">
|
||||
<template #default="scope">
|
||||
<span class="text-success">{{ scope.row.completedCount }}</span>
|
||||
<!-- 标签页 -->
|
||||
<el-tabs v-model="activeTab" class="task-tabs" @tab-change="handleTabChange">
|
||||
<!-- 任务概览 -->
|
||||
<el-tab-pane label="任务概览" name="overview">
|
||||
<!-- 完成进度 -->
|
||||
<el-card class="mb-20px">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>完成进度</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="完成率" align="center" width="150">
|
||||
<template #default="scope">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<div class="progress-item">
|
||||
<div class="progress-value">{{ taskProgress?.totalCount || 0 }}</div>
|
||||
<div class="progress-label">目标人数</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="progress-item">
|
||||
<div class="progress-value text-success">{{ taskProgress?.completedCount || 0 }}</div>
|
||||
<div class="progress-label">已完成</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="progress-item">
|
||||
<div class="progress-value text-warning">{{ taskProgress?.pendingCount || 0 }}</div>
|
||||
<div class="progress-label">待完成</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="progress-item">
|
||||
<div class="progress-value text-primary">{{ taskProgress?.completionRate }}%</div>
|
||||
<div class="progress-label">完成率</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="mt-20px">
|
||||
<el-progress
|
||||
:percentage="Number(scope.row.completionRate) || 0"
|
||||
:stroke-width="6"
|
||||
:percentage="Number(taskProgress?.completionRate) || 0"
|
||||
:stroke-width="20"
|
||||
status="success"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 状态分布 -->
|
||||
<el-row :gutter="20" class="mt-20px">
|
||||
<el-col :span="8">
|
||||
<el-statistic title="待测评" :value="taskProgress?.statusBreakdown?.pending || 0">
|
||||
<template #suffix>
|
||||
<span class="text-gray">人</span>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-statistic title="测评中" :value="taskProgress?.statusBreakdown?.inProgress || 0">
|
||||
<template #suffix>
|
||||
<span class="text-gray">人</span>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-statistic title="已完成" :value="taskProgress?.statusBreakdown?.completed || 0">
|
||||
<template #suffix>
|
||||
<span class="text-gray">人</span>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<!-- 按监区统计 -->
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>按监区统计</span>
|
||||
<el-button
|
||||
v-if="taskProgress?.pendingCount > 0"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleRemind"
|
||||
:loading="remindLoading"
|
||||
>
|
||||
提醒未完成
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="平均分" align="center" prop="avgScore" width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.avgScore || '-' }}
|
||||
|
||||
<el-table :data="areaStatistics" stripe>
|
||||
<el-table-column label="监区" align="center" prop="areaName" width="120" />
|
||||
<el-table-column label="目标人数" align="center" prop="totalCount" width="100" />
|
||||
<el-table-column label="已完成" align="center" prop="completedCount" width="100">
|
||||
<template #default="scope">
|
||||
<span class="text-success">{{ scope.row.completedCount }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="完成率" align="center" width="150">
|
||||
<template #default="scope">
|
||||
<el-progress
|
||||
:percentage="Number(scope.row.completionRate) || 0"
|
||||
:stroke-width="6"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="平均分" align="center" prop="avgScore" width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.avgScore || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="及格率" align="center" prop="passRate" width="120">
|
||||
<template #default="scope">
|
||||
{{ scope.row.passRate ? scope.row.passRate + '%' : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="风险分布" align="center" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-tag type="danger" size="small" class="mr-5px">
|
||||
高{{ scope.row.riskDistribution?.highRisk || 0 }}
|
||||
</el-tag>
|
||||
<el-tag type="warning" size="small" class="mr-5px">
|
||||
中{{ scope.row.riskDistribution?.mediumRisk || 0 }}
|
||||
</el-tag>
|
||||
<el-tag type="success" size="small">
|
||||
低{{ scope.row.riskDistribution?.lowRisk || 0 }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 人员填写进度 -->
|
||||
<el-tab-pane label="人员填写进度" name="prisoners">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>人员填写进度列表</span>
|
||||
<div class="filter-actions">
|
||||
<el-select
|
||||
v-model="prisonerFilter.status"
|
||||
placeholder="填写状态"
|
||||
clearable
|
||||
size="small"
|
||||
class="filter-select"
|
||||
@change="handlePrisonerFilterChange"
|
||||
>
|
||||
<el-option label="全部" :value="undefined" />
|
||||
<el-option label="待测评" :value="1" />
|
||||
<el-option label="测评中" :value="2" />
|
||||
<el-option label="已完成" :value="3" />
|
||||
<el-option label="已取消" :value="4" />
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="prisonerFilter.areaId"
|
||||
placeholder="监区"
|
||||
clearable
|
||||
size="small"
|
||||
class="filter-select"
|
||||
@change="handlePrisonerFilterChange"
|
||||
>
|
||||
<el-option label="全部" :value="undefined" />
|
||||
<el-option
|
||||
v-for="area in areaStatistics"
|
||||
:key="area.areaId"
|
||||
:label="area.areaName"
|
||||
:value="area.areaId ?? ''"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="prisonerFilter.riskLevel"
|
||||
placeholder="风险等级"
|
||||
clearable
|
||||
size="small"
|
||||
class="filter-select"
|
||||
@change="handlePrisonerFilterChange"
|
||||
>
|
||||
<el-option label="全部" :value="undefined" />
|
||||
<el-option label="低风险" :value="1" />
|
||||
<el-option label="中风险" :value="2" />
|
||||
<el-option label="高风险" :value="3" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="及格率" align="center" prop="passRate" width="120">
|
||||
<template #default="scope">
|
||||
{{ scope.row.passRate ? scope.row.passRate + '%' : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="风险分布" align="center" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-tag type="danger" size="small" class="mr-5px">
|
||||
高{{ scope.row.riskDistribution?.highRisk || 0 }}
|
||||
</el-tag>
|
||||
<el-tag type="warning" size="small" class="mr-5px">
|
||||
中{{ scope.row.riskDistribution?.mediumRisk || 0 }}
|
||||
</el-tag>
|
||||
<el-tag type="success" size="small">
|
||||
低{{ scope.row.riskDistribution?.lowRisk || 0 }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 统计摘要 -->
|
||||
<el-row :gutter="20" class="mb-20px">
|
||||
<el-col :span="6">
|
||||
<el-statistic title="总人数" :value="filteredPrisoners.length">
|
||||
<template #suffix>
|
||||
<span class="text-gray">人</span>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="待测评" :value="prisonerStats.pending">
|
||||
<template #suffix>
|
||||
<span class="text-gray">人</span>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="测评中" :value="prisonerStats.inProgress">
|
||||
<template #suffix>
|
||||
<span class="text-gray">人</span>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="已完成" :value="prisonerStats.completed">
|
||||
<template #suffix>
|
||||
<span class="text-gray">人</span>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 人员列表 -->
|
||||
<el-table :data="filteredPrisoners" stripe v-loading="prisonersLoading">
|
||||
<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="areaName" width="120" />
|
||||
<el-table-column label="填写状态" align="center" prop="status" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTag(scope.row.status)" size="small">
|
||||
{{ getStatusLabel(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="客观分" align="center" prop="objectiveScore" width="90" />
|
||||
<el-table-column label="主观分" align="center" prop="subjectiveScore" 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">
|
||||
<el-tag v-if="scope.row.riskLevel" :type="getRiskTag(scope.row.riskLevel)" size="small">
|
||||
{{ getRiskLabel(scope.row.riskLevel) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</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="finishTime" width="160">
|
||||
<template #default="scope">
|
||||
{{ scope.row.finishTime ? formatDateTime(scope.row.finishTime) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="320" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.status === 3"
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleViewAnswer(scope.row)"
|
||||
>
|
||||
查看答案
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === 1"
|
||||
link
|
||||
type="success"
|
||||
size="small"
|
||||
@click="handleNotifyPrisoner(scope.row)"
|
||||
>
|
||||
通知
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === 1 || scope.row.status === 2"
|
||||
link
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="handleAgentFill(scope.row)"
|
||||
v-hasPermi="['prison:questionnaire-record:agent-fill']"
|
||||
>
|
||||
代填
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === 2 || scope.row.status === 3"
|
||||
link
|
||||
type="info"
|
||||
size="small"
|
||||
@click="handleResetRecord(scope.row)"
|
||||
>
|
||||
重置
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 答案详情弹窗 -->
|
||||
<AnswerDetailDialog ref="answerDetailDialogRef" />
|
||||
|
||||
<!-- 代填弹窗 -->
|
||||
<AgentFillDialog ref="agentFillDialogRef" @success="loadPrisonerProgress" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { formatDateTime } from '@/utils/formatTime'
|
||||
import AnswerDetailDialog from '@/views/prison/questionnairerecord/AnswerDetailDialog.vue'
|
||||
import AgentFillDialog from './AgentFillDialog.vue'
|
||||
import { QuestionnaireTaskApi } from '@/api/prison/questionnaire-task'
|
||||
|
||||
defineOptions({ name: 'TaskDetailDialog' })
|
||||
@ -165,7 +343,15 @@ defineOptions({ name: 'TaskDetailDialog' })
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const remindLoading = ref(false)
|
||||
const prisonersLoading = ref(false)
|
||||
const taskId = ref<number>()
|
||||
const activeTab = ref('overview')
|
||||
|
||||
// 答案详情弹窗
|
||||
const answerDetailDialogRef = ref()
|
||||
|
||||
// 代填弹窗
|
||||
const agentFillDialogRef = ref()
|
||||
|
||||
// 任务详情
|
||||
const taskDetail = ref<any>(null)
|
||||
@ -176,6 +362,16 @@ const taskProgress = ref<any>(null)
|
||||
// 按监区统计
|
||||
const areaStatistics = ref<any[]>([])
|
||||
|
||||
// 人员填写进度列表
|
||||
const prisonerProgressList = ref<any[]>([])
|
||||
|
||||
// 人员筛选条件
|
||||
const prisonerFilter = reactive({
|
||||
status: undefined as number | undefined,
|
||||
areaId: undefined as number | undefined,
|
||||
riskLevel: undefined as number | undefined
|
||||
})
|
||||
|
||||
// 任务状态选项
|
||||
const taskStatusOptions = [
|
||||
{ value: 1, label: '草稿', type: 'info' },
|
||||
@ -184,6 +380,44 @@ const taskStatusOptions = [
|
||||
{ value: 4, label: '已取消', type: 'danger' }
|
||||
]
|
||||
|
||||
// 风险等级选项
|
||||
const riskLevelOptions = [
|
||||
{ value: 1, label: '低风险', type: 'success' },
|
||||
{ value: 2, label: '中风险', type: 'warning' },
|
||||
{ value: 3, label: '高风险', type: 'danger' }
|
||||
]
|
||||
|
||||
/** 筛选后的人员列表 */
|
||||
const filteredPrisoners = computed(() => {
|
||||
return prisonerProgressList.value.filter(item => {
|
||||
if (prisonerFilter.status !== undefined && item.status !== prisonerFilter.status) {
|
||||
return false
|
||||
}
|
||||
if (prisonerFilter.areaId !== undefined && item.areaId !== prisonerFilter.areaId) {
|
||||
return false
|
||||
}
|
||||
if (prisonerFilter.riskLevel !== undefined && item.riskLevel !== prisonerFilter.riskLevel) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
/** 人员统计 */
|
||||
const prisonerStats = computed(() => {
|
||||
const stats = {
|
||||
pending: 0,
|
||||
inProgress: 0,
|
||||
completed: 0
|
||||
}
|
||||
filteredPrisoners.value.forEach(item => {
|
||||
if (item.status === 1) stats.pending++
|
||||
else if (item.status === 2) stats.inProgress++
|
||||
else if (item.status === 3) stats.completed++
|
||||
})
|
||||
return stats
|
||||
})
|
||||
|
||||
/** 获取状态标签类型 */
|
||||
const getStatusTag = (status: number | undefined) => {
|
||||
if (!status) return 'info'
|
||||
@ -198,10 +432,37 @@ const getStatusLabel = (status: number | undefined) => {
|
||||
return item?.label || '未知'
|
||||
}
|
||||
|
||||
/** 获取风险标签类型 */
|
||||
const getRiskTag = (level: number) => {
|
||||
const item = riskLevelOptions.find(item => item.value === level)
|
||||
return item?.type || 'info'
|
||||
}
|
||||
|
||||
/** 获取风险标签文本 */
|
||||
const getRiskLabel = (level: number) => {
|
||||
const item = riskLevelOptions.find(item => item.value === level)
|
||||
return item?.label || '-'
|
||||
}
|
||||
|
||||
/** 格式化时长 */
|
||||
const formatDuration = (seconds: number) => {
|
||||
const hours = Math.floor(seconds / 3600)
|
||||
const minutes = Math.floor((seconds % 3600) / 60)
|
||||
const secs = seconds % 60
|
||||
if (hours > 0) {
|
||||
return `${hours}时${minutes}分${secs}秒`
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}分${secs}秒`
|
||||
} else {
|
||||
return `${secs}秒`
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
taskId.value = id
|
||||
activeTab.value = 'overview'
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
@ -229,6 +490,89 @@ const open = async (id: number) => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 切换到人员标签页时加载人员数据 */
|
||||
const loadPrisonerProgress = async () => {
|
||||
if (!taskId.value) return
|
||||
|
||||
prisonersLoading.value = true
|
||||
try {
|
||||
const data = await QuestionnaireTaskApi.getPrisonerProgress(taskId.value)
|
||||
prisonerProgressList.value = data || []
|
||||
} catch (e) {
|
||||
console.error('获取人员进度失败', e)
|
||||
ElMessage.error('获取人员进度失败')
|
||||
} finally {
|
||||
prisonersLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听标签页切换 */
|
||||
const handleTabChange = (tabName: string) => {
|
||||
if (tabName === 'prisoners' && prisonerProgressList.value.length === 0) {
|
||||
loadPrisonerProgress()
|
||||
}
|
||||
}
|
||||
|
||||
/** 筛选条件变化 */
|
||||
const handlePrisonerFilterChange = () => {
|
||||
// 筛选是自动的,通过计算属性实现
|
||||
}
|
||||
|
||||
/** 查看答案 */
|
||||
const handleViewAnswer = (row: any) => {
|
||||
if (!row.id) {
|
||||
ElMessage.warning('该人员暂无答题记录')
|
||||
return
|
||||
}
|
||||
answerDetailDialogRef.value?.open(row.id)
|
||||
}
|
||||
|
||||
/** 通知人员 */
|
||||
const handleNotifyPrisoner = async (row: any) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要通知「${row.prisonerName}」完成问卷吗?`,
|
||||
'通知确认',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
}
|
||||
)
|
||||
|
||||
await QuestionnaireTaskApi.notifyPrisoner(row.id)
|
||||
ElMessage.success('通知已发送')
|
||||
} catch (e) {
|
||||
// 用户取消
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置答题记录 */
|
||||
const handleResetRecord = async (row: any) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要重置「${row.prisonerName}」的答题记录吗?重置后需要重新填写。`,
|
||||
'重置确认',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
|
||||
await QuestionnaireTaskApi.resetPrisonerRecord(row.id)
|
||||
ElMessage.success('重置成功')
|
||||
loadPrisonerProgress()
|
||||
} catch (e) {
|
||||
// 用户取消
|
||||
}
|
||||
}
|
||||
|
||||
/** 代为填写 */
|
||||
const handleAgentFill = (row: any) => {
|
||||
agentFillDialogRef.value?.open(row)
|
||||
}
|
||||
|
||||
/** 提醒未完成人员 */
|
||||
const handleRemind = async () => {
|
||||
if (!taskId.value) return
|
||||
@ -290,4 +634,26 @@ defineExpose({ open })
|
||||
.text-gray {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
.filter-select {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-tabs {
|
||||
:deep(.el-tabs__content) {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -52,6 +52,23 @@
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleCreate"
|
||||
v-hasPermi="['prison:questionnaire-task:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 创建任务
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['prison:questionnaire-task:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
@ -37,6 +37,9 @@
|
||||
<span class="label">状态</span>
|
||||
<span class="value">{{ getPassStatusText(recordInfo.passStatus) }}</span>
|
||||
</div>
|
||||
<el-button type="primary" link @click="handlePreviewQuestionnaire">
|
||||
<Icon icon="ep:view" class="mr-3px" />查看问卷
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -163,6 +166,19 @@
|
||||
<el-empty v-if="partitions.length === 0 && !loading" description="暂无答题记录" />
|
||||
</div>
|
||||
|
||||
<!-- 问卷预览弹窗 -->
|
||||
<el-dialog
|
||||
v-model="previewVisible"
|
||||
title="问卷预览"
|
||||
width="800px"
|
||||
destroy-on-close
|
||||
>
|
||||
<QuestionnairePreview :id="previewQuestionnaireId" />
|
||||
<template #footer>
|
||||
<el-button @click="previewVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">关 闭</el-button>
|
||||
</template>
|
||||
@ -176,6 +192,7 @@ import { formatDateTime } from '@/utils/formatTime'
|
||||
import { QuestionnaireRecordApi, type QuestionnaireRecord } from '@/api/prison/questionnairerecord'
|
||||
import { QuestionApi, type Question } from '@/api/prison/question'
|
||||
import { AnswerApi, type Answer } from '@/api/prison/answer'
|
||||
import QuestionnairePreview from '@/views/prison/questionnaire/components/QuestionnairePreview.vue'
|
||||
import { getIntDictOptions } from '@/utils/dict'
|
||||
|
||||
defineOptions({ name: 'AnswerDetailDialog' })
|
||||
@ -184,6 +201,10 @@ const dialogVisible = ref(false)
|
||||
const title = ref('答题详情')
|
||||
const loading = ref(false)
|
||||
|
||||
// 预览相关
|
||||
const previewVisible = ref(false)
|
||||
const previewQuestionnaireId = ref<number>()
|
||||
|
||||
// 记录信息
|
||||
const recordInfo = ref<QuestionnaireRecord>({
|
||||
id: undefined,
|
||||
@ -349,6 +370,15 @@ const open = async (recordId: number) => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 预览问卷 */
|
||||
const handlePreviewQuestionnaire = () => {
|
||||
if (!recordInfo.value.questionnaireId) {
|
||||
return
|
||||
}
|
||||
previewQuestionnaireId.value = recordInfo.value.questionnaireId
|
||||
previewVisible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user