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/task/Detail.vue |  192 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 192 insertions(+), 0 deletions(-)

diff --git a/apps/jnpf-web-apps-main/src/views/x/tms/task/Detail.vue b/apps/jnpf-web-apps-main/src/views/x/tms/task/Detail.vue
new file mode 100644
index 0000000..3336f36
--- /dev/null
+++ b/apps/jnpf-web-apps-main/src/views/x/tms/task/Detail.vue
@@ -0,0 +1,192 @@
+<script lang="ts" setup>
+import type { TaskEntity } from './types';
+
+import { 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 { getUserInfoList } from '#/api/permission/user';
+import { getTaskInfo } from '#/api/x/tms/task';
+import { TMS_BTN } from '#/views/x/tms/shared/ui';
+
+import {
+  labelOfCategory,
+  labelOfEvalMode,
+  labelOfPersonStatus,
+  labelOfPublishStatus,
+  labelOfSourceType,
+  labelOfTaskKind,
+  labelOfTrainMode,
+  loadTaskDics,
+  publishStatusColor,
+} from './constants';
+
+import '#/views/x/tms/shared/page.css';
+
+defineOptions({ name: 'TmsTaskDetail' });
+
+const route = useRoute();
+const router = useRouter();
+const { createMessage } = useMessage();
+
+const loading = ref(false);
+const detail = ref<TaskEntity | null>(null);
+
+onMounted(async () => {
+  await loadTaskDics();
+  await loadData();
+});
+
+async function loadData() {
+  const id = String(route.params.id || '');
+  if (!id) {
+    router.replace('/tms/task');
+    return;
+  }
+  loading.value = true;
+  try {
+    const info = await getTaskInfo(id);
+    await fillUserNames(info);
+    detail.value = info;
+  } catch (e: any) {
+    createMessage.error(e?.message || '鍔犺浇澶辫触');
+    router.replace('/tms/task');
+  } finally {
+    loading.value = false;
+  }
+}
+
+async function fillUserNames(info: TaskEntity) {
+  const ids = [
+    info.teacherId,
+    ...(info.personTasks || []).map((item) => item.userId),
+  ].filter((id): id is string => !!id);
+  const uniqueIds = [...new Set(ids)];
+  if (!uniqueIds.length) return;
+  const res = await getUserInfoList(uniqueIds);
+  const list = res?.data?.list || [];
+  const nameOf = new Map<string, string>();
+  for (const user of list) {
+    const name = user.realName || String(user.fullName || '').split('/')[0] || user.account;
+    if (user.id && name) nameOf.set(user.id, name);
+  }
+  info.teacherName = nameOf.get(info.teacherId || '') || info.teacherId;
+  for (const item of info.personTasks || []) {
+    item.userName = nameOf.get(item.userId || '') || item.userId;
+  }
+}
+
+function goBack() {
+  router.push('/tms/task');
+}
+
+function goEdit() {
+  if (!detail.value?.id || detail.value.publishStatus !== 'draft') return;
+  router.push(`/tms/task/edit/${detail.value.id}`);
+}
+</script>
+
+<template>
+  <div class="jnpf-content-wrapper tms-task-detail-page" v-loading="loading">
+    <div class="jnpf-content-wrapper-center">
+      <div class="jnpf-content-wrapper-content tms-task-detail-wrap" v-if="detail">
+        <a-card title="鍩硅浠诲姟璇︽儏" :bordered="false">
+          <template #extra>
+            <a-space>
+              <a-button @click="goBack">{{ TMS_BTN.back }}</a-button>
+              <a-button
+                type="primary"
+                :disabled="detail.publishStatus !== 'draft'"
+                @click="goEdit"
+              >
+                {{ TMS_BTN.edit }}
+              </a-button>
+            </a-space>
+          </template>
+
+          <ADescriptions :column="2" bordered size="small">
+            <ADescriptionsItem label="鍩硅缂栧彿">{{ detail.taskNo || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鐘舵��">
+              <a-tag :color="publishStatusColor(detail.publishStatus)">
+                {{ labelOfPublishStatus(detail.publishStatus) }}
+              </a-tag>
+            </ADescriptionsItem>
+            <ADescriptionsItem label="鍩硅涓婚" :span="2">{{ detail.subject }}</ADescriptionsItem>
+            <ADescriptionsItem label="鍩硅鍒嗙被">{{ labelOfCategory(detail.category) }}</ADescriptionsItem>
+            <ADescriptionsItem label="鏉ユ簮绫诲瀷">{{ labelOfSourceType(detail.sourceType) }}</ADescriptionsItem>
+            <ADescriptionsItem label="鍩硅鏂瑰紡">{{ labelOfTrainMode(detail.trainMode) }}</ADescriptionsItem>
+            <ADescriptionsItem label="鑰冩牳鏂瑰紡">{{ labelOfEvalMode(detail.evalMode) }}</ADescriptionsItem>
+            <ADescriptionsItem label="浠诲姟绫诲瀷">{{ labelOfTaskKind(detail.taskKind) }}</ADescriptionsItem>
+            <ADescriptionsItem label="涓婃浠诲姟缂栧彿">{{ detail.prevTaskNo || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="寮�濮嬫椂闂�">{{ detail.startTime || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="缁撴潫鏃堕棿">{{ detail.endTime || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鍏抽棴鏃堕棿">{{ detail.closeTime || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鍙戝竷鏃堕棿">{{ detail.publishTime || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鍦扮偣">{{ detail.placeName || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鏁欐潗缂栧彿">{{ detail.materialNo || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="闄勪欢" :span="2">
+              {{ detail.files?.map((item) => item.fileName).filter(Boolean).join('銆�') || '-' }}
+            </ADescriptionsItem>
+            <ADescriptionsItem label="璇曞嵎">{{ detail.paperName || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="琛ヨ�冩鏁�">{{ detail.retakeLimit ?? 1 }}</ADescriptionsItem>
+            <ADescriptionsItem label="鍚堟牸鍒�">{{ detail.passScore ?? '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鑰冭瘯寮�濮�">{{ detail.examStart || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鑰冭瘯缁撴潫">{{ detail.examEnd || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鍩硅甯�">{{ detail.teacherName || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鍩硅瀵硅薄鏁�">
+              {{ detail.personCount ?? detail.traineeIds?.length ?? '-' }}
+            </ADescriptionsItem>
+            <ADescriptionsItem label="鍩硅瑕佺偣" :span="2">{{ detail.keyPoints || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="澶囨敞" :span="2">{{ detail.remark || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.creatorTime || '-' }}</ADescriptionsItem>
+          </ADescriptions>
+
+          <a-divider>涓汉浠诲姟</a-divider>
+          <a-table
+            size="small"
+            :pagination="false"
+            row-key="id"
+            :data-source="detail.personTasks || []"
+            :columns="[
+              { title: '鐢ㄦ埛鍚嶇О', dataIndex: 'userName', width: 160 },
+              { title: '鐘舵��', dataIndex: 'bizStatus', width: 100 },
+              { title: '绛惧埌鏃堕棿', dataIndex: 'signTime', width: 170 },
+              { title: '鎴愮哗', dataIndex: 'examScore', width: 90 },
+              { title: '鍚堟牸', dataIndex: 'passFlag', width: 90 },
+            ]"
+          >
+            <template #bodyCell="{ column, record }">
+              <template v-if="column.dataIndex === 'bizStatus'">
+                {{ labelOfPersonStatus(record.bizStatus) }}
+              </template>
+              <template v-else-if="column.dataIndex === 'passFlag'">
+                {{ record.passFlag === '1' ? '鍚堟牸' : record.passFlag === '0' ? '涓嶅悎鏍�' : '-' }}
+              </template>
+            </template>
+          </a-table>
+        </a-card>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.tms-task-detail-page {
+  height: 100%;
+  min-height: 0;
+}
+
+.tms-task-detail-wrap {
+  height: 100%;
+  min-height: 0;
+  overflow-x: hidden;
+  overflow-y: auto !important;
+  background: #fff;
+  box-sizing: border-box;
+}
+</style>

--
Gitblit v1.8.0