From 8534025c45b4736975730678b9ccf23570a75f95 Mon Sep 17 00:00:00 2001
From: liuyu <yu.liu@cqgbkj.com>
Date: 星期二, 22 九月 2026 09:25:48 +0800
Subject: [PATCH] feat(tms): 新增试卷、培训任务、个人任务页面
---
apps/jnpf-web-apps-main/src/views/x/tms/personTask/Detail.vue | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 205 insertions(+), 0 deletions(-)
diff --git a/apps/jnpf-web-apps-main/src/views/x/tms/personTask/Detail.vue b/apps/jnpf-web-apps-main/src/views/x/tms/personTask/Detail.vue
new file mode 100644
index 0000000..d3b72ec
--- /dev/null
+++ b/apps/jnpf-web-apps-main/src/views/x/tms/personTask/Detail.vue
@@ -0,0 +1,205 @@
+<script lang="ts" setup>
+import type { PersonTaskItem } from './types';
+
+import { computed, onMounted, ref } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+
+import { useMessage } from '@jnpf/hooks';
+import { Descriptions as ADescriptions, DescriptionsItem as ADescriptionsItem } from 'ant-design-vue';
+
+import { getPersonTaskInfo, signPersonTask } from '#/api/x/tms/personTask';
+import { TMS_BTN } from '#/views/x/tms/shared/ui';
+
+import {
+ labelOfCategory,
+ labelOfEvalMode,
+ labelOfPersonStatus,
+ labelOfTrainMode,
+ loadPersonTaskDics,
+} from './constants';
+
+import '#/views/x/tms/shared/page.css';
+
+defineOptions({ name: 'TmsPersonTaskDetail' });
+
+const route = useRoute();
+const router = useRouter();
+const { createMessage } = useMessage();
+
+const loading = ref(false);
+const signing = ref(false);
+const detail = ref<PersonTaskItem | null>(null);
+
+const progressText = computed(() => {
+ const learned = detail.value?.learnedSeconds || 0;
+ const required = detail.value?.requiredSeconds || 0;
+ return `${formatDuration(learned)} / ${required > 0 ? formatDuration(required) : '鏈厤缃�'}`;
+});
+
+const isClosed = computed(() => detail.value?.bizStatus === 'cancelled' || detail.value?.bizStatus === 'expired');
+const canLearn = computed(() => Boolean(detail.value?.id) && !isClosed.value);
+const showExam = computed(() => Boolean(detail.value?.canExam && detail.value?.paperId));
+const tipMessage = computed(() => {
+ if (!detail.value) return '';
+ if (detail.value.bizStatus === 'cancelled') return '浠诲姟宸插彇娑堬紝鏃犳硶缁х画瀛︿範鎴栬�冭瘯銆�';
+ if (detail.value.bizStatus === 'expired') return '浠诲姟宸茶繃鏈燂紝鏃犳硶缁х画瀛︿範鎴栬�冭瘯銆�';
+ if (detail.value.examTip && !detail.value.canExam) return detail.value.examTip;
+ if (detail.value.signTip && detail.value.canSign) return detail.value.signTip;
+ return '';
+});
+
+onMounted(async () => {
+ await loadPersonTaskDics();
+ await loadData();
+});
+
+async function loadData() {
+ const id = String(route.params.id || '');
+ if (!id) {
+ router.replace('/tms/personTask');
+ return;
+ }
+ loading.value = true;
+ try {
+ detail.value = await getPersonTaskInfo(id);
+ } catch (e: any) {
+ createMessage.error(e?.message || '鍔犺浇澶辫触');
+ router.replace('/tms/personTask');
+ } finally {
+ loading.value = false;
+ }
+}
+
+function goBack() {
+ router.push('/tms/personTask');
+}
+
+function goLearn() {
+ if (!detail.value?.id) return;
+ if (isClosed.value) {
+ createMessage.warning(tipMessage.value || '褰撳墠浠诲姟涓嶅彲瀛︿範');
+ return;
+ }
+ router.push(`/tms/personTask/learn/${detail.value.id}`);
+}
+
+function goExam() {
+ if (!detail.value?.id || !showExam.value) {
+ createMessage.info(detail.value?.examTip || '褰撳墠鏆備笉鍙�冭瘯');
+ return;
+ }
+ router.push(`/tms/personTask/learn/${detail.value.id}`);
+}
+
+async function handleSign() {
+ if (!detail.value?.id || !detail.value.canSign || signing.value) return;
+ signing.value = true;
+ try {
+ const result = await signPersonTask(detail.value.id);
+ detail.value.signTime = result.signTime;
+ detail.value.signed = result.signed ?? true;
+ detail.value.canSign = result.canSign ?? false;
+ detail.value.signTip = result.signTip || '宸茬鍒�';
+ detail.value.bizStatus = result.bizStatus || detail.value.bizStatus;
+ detail.value.canExam = result.canExam;
+ detail.value.examTip = result.examTip;
+ createMessage.success(result.message || '绛惧埌鎴愬姛');
+ } catch (e: any) {
+ createMessage.error(e?.message || '绛惧埌澶辫触');
+ } finally {
+ signing.value = false;
+ }
+}
+
+function formatDuration(seconds: number) {
+ const h = Math.floor(seconds / 3600);
+ const m = Math.floor((seconds % 3600) / 60);
+ const s = seconds % 60;
+ if (h > 0) return `${h}灏忔椂${m}鍒哷;
+ if (m > 0) return `${m}鍒�${s}绉抈;
+ return `${s}绉抈;
+}
+</script>
+
+<template>
+ <div class="jnpf-content-wrapper tms-person-task-page" v-loading="loading">
+ <div class="jnpf-content-wrapper-center">
+ <div class="jnpf-content-wrapper-content tms-person-task-wrap" v-if="detail">
+ <div class="tms-page-header">
+ <div>
+ <div class="tms-page-header__title">涓汉鍩硅浠诲姟</div>
+ <div class="tms-page-header__sub">{{ detail.taskNo || '-' }} 路 {{ detail.subject || '-' }}</div>
+ </div>
+ <div class="tms-page-header__actions">
+ <a-button @click="goBack">{{ TMS_BTN.back }}</a-button>
+ <a-button
+ v-if="detail.canSign"
+ type="primary"
+ :ghost="showExam"
+ :loading="signing"
+ @click="handleSign"
+ >
+ {{ TMS_BTN.sign }}
+ </a-button>
+ <a-button v-if="canLearn" type="primary" ghost @click="goLearn">{{ TMS_BTN.enterLearn }}</a-button>
+ <a-button v-if="showExam" type="primary" @click="goExam">
+ {{ detail.passFlag === '0' ? TMS_BTN.retake : TMS_BTN.enterExam }}
+ </a-button>
+ </div>
+ </div>
+
+ <a-alert v-if="tipMessage" class="tms-page-tip" type="warning" show-icon :message="tipMessage" />
+
+ <ADescriptions bordered :column="2" size="small">
+ <ADescriptionsItem label="鍩硅缂栧彿">{{ detail.taskNo || '-' }}</ADescriptionsItem>
+ <ADescriptionsItem label="鐘舵��">{{ labelOfPersonStatus(detail.bizStatus) }}</ADescriptionsItem>
+ <ADescriptionsItem label="鍩硅涓婚" :span="2">{{ detail.subject || '-' }}</ADescriptionsItem>
+ <ADescriptionsItem label="鍩硅鍒嗙被">{{ labelOfCategory(detail.category) }}</ADescriptionsItem>
+ <ADescriptionsItem label="鍩硅鏂瑰紡">{{ labelOfTrainMode(detail.trainMode) }}</ADescriptionsItem>
+ <ADescriptionsItem label="鑰冩牳鏂瑰紡">{{ labelOfEvalMode(detail.evalMode) }}</ADescriptionsItem>
+ <ADescriptionsItem label="绛惧埌鐘舵��">
+ {{
+ detail.signed || detail.signTime
+ ? `宸茬鍒�${detail.signTime ? `锛�${detail.signTime}锛塦 : ''}`
+ : detail.signTip || '鏈鍒�'
+ }}
+ </ADescriptionsItem>
+ <ADescriptionsItem label="鏄惁鍙��">
+ {{ detail.canExam ? '鏄�' : '鍚�' }}
+ <span v-if="!detail.canExam && detail.examTip" class="tip-inline">锛坽{ detail.examTip }}锛�</span>
+ </ADescriptionsItem>
+ <ADescriptionsItem label="瀛︿範杩涘害">{{ detail.learnStatus || '-' }}</ADescriptionsItem>
+ <ADescriptionsItem label="瑕佹眰璇炬椂">{{ detail.requiredHours ?? '-' }} 灏忔椂</ADescriptionsItem>
+ <ADescriptionsItem label="宸插/闇�瀛�">{{ progressText }}</ADescriptionsItem>
+ <ADescriptionsItem label="寮�濮嬫椂闂�">{{ detail.startTime || '-' }}</ADescriptionsItem>
+ <ADescriptionsItem label="缁撴潫鏃堕棿">{{ detail.endTime || '-' }}</ADescriptionsItem>
+ <ADescriptionsItem label="鍏抽棴鏃堕棿">{{ detail.closeTime || '-' }}</ADescriptionsItem>
+ <ADescriptionsItem label="鍦扮偣">{{ detail.placeName || '-' }}</ADescriptionsItem>
+ <ADescriptionsItem label="璇曞嵎">{{ detail.paperName || '-' }}</ADescriptionsItem>
+ <ADescriptionsItem label="鎴愮哗">{{ detail.examScore ?? '-' }}</ADescriptionsItem>
+ <ADescriptionsItem label="鍩硅瑕佺偣" :span="2">{{ detail.keyPoints || '-' }}</ADescriptionsItem>
+ </ADescriptions>
+ </div>
+ </div>
+ </div>
+</template>
+
+<style scoped>
+.tms-person-task-page,
+.tms-person-task-wrap {
+ height: 100%;
+ min-height: 0;
+}
+
+.tms-person-task-wrap {
+ overflow: auto;
+ background: #fff;
+ padding: 16px 20px 24px;
+ box-sizing: border-box;
+}
+
+.tip-inline {
+ color: #fa8c16;
+ margin-left: 4px;
+}
+</style>
--
Gitblit v1.8.0