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

diff --git a/apps/jnpf-web-apps-main/src/views/x/tms/paper/Detail.vue b/apps/jnpf-web-apps-main/src/views/x/tms/paper/Detail.vue
new file mode 100644
index 0000000..8f316dd
--- /dev/null
+++ b/apps/jnpf-web-apps-main/src/views/x/tms/paper/Detail.vue
@@ -0,0 +1,163 @@
+<script lang="ts" setup>
+import type { PaperEntity } 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 { getPaperInfo } from '#/api/x/tms/paper';
+
+import {
+  labelOfSortMode,
+  labelOfStatus,
+  labelOfType,
+  loadPaperDics,
+} from './constants';
+
+defineOptions({ name: 'TmsPaperDetail' });
+
+const route = useRoute();
+const router = useRouter();
+const { createMessage } = useMessage();
+
+const loading = ref(false);
+const detail = ref<PaperEntity | null>(null);
+
+onMounted(async () => {
+  await loadPaperDics();
+  await loadData();
+});
+
+async function loadData() {
+  const id = String(route.params.id || '');
+  if (!id) {
+    router.replace('/tms/paper');
+    return;
+  }
+  loading.value = true;
+  try {
+    detail.value = await getPaperInfo(id);
+  } catch (e: any) {
+    createMessage.error(e?.message || '鍔犺浇璇曞嵎澶辫触');
+    router.replace('/tms/paper');
+  } finally {
+    loading.value = false;
+  }
+}
+
+function goBack() {
+  router.push('/tms/paper');
+}
+
+function goEdit() {
+  if (!detail.value?.id || detail.value.bizStatus !== 'closed') return;
+  router.push(`/tms/paper/edit/${detail.value.id}`);
+}
+
+function stripHtml(html?: string) {
+  if (!html) return '';
+  return html.replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').trim();
+}
+</script>
+
+<template>
+  <div class="jnpf-content-wrapper tms-paper-detail-page" v-loading="loading">
+    <div class="jnpf-content-wrapper-center">
+      <div class="jnpf-content-wrapper-content tms-paper-detail-wrap" v-if="detail">
+        <a-card title="璇曞嵎璇︽儏" :bordered="false">
+          <template #extra>
+            <a-space>
+              <a-button @click="goBack">杩斿洖</a-button>
+              <a-button
+                type="primary"
+                :disabled="detail.bizStatus !== 'closed'"
+                @click="goEdit"
+              >
+                缂栬緫
+              </a-button>
+            </a-space>
+          </template>
+
+          <ADescriptions :column="2" bordered size="small">
+            <ADescriptionsItem label="璇曞嵎缂栧彿">{{ detail.paperNo || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="璇曞嵎鍚嶇О">{{ detail.paperName }}</ADescriptionsItem>
+            <ADescriptionsItem label="鐘舵��">{{ labelOfStatus(detail.bizStatus) }}</ADescriptionsItem>
+            <ADescriptionsItem label="鑰冭瘯鏃堕暱">{{ detail.durationMin ?? '-' }} 鍒嗛挓</ADescriptionsItem>
+            <ADescriptionsItem label="璇曢鎺掑簭">{{ labelOfSortMode(detail.sortMode) }}</ADescriptionsItem>
+            <ADescriptionsItem label="寮�鑰冩椂闂�">{{ detail.examStart || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="缁撴潫鏃堕棿">{{ detail.examEnd || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鎴愮哗鍏竷鏃堕棿">{{ detail.scorePublishTime || '绔嬪嵆鍏竷' }}</ADescriptionsItem>
+            <ADescriptionsItem label="璇曞嵎鎬诲垎">{{ detail.totalScore ?? '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鍚堟牸鍒嗘暟">{{ detail.passScore ?? '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鍚富瑙傞">{{ detail.hasSubjective === '1' ? '鏄�' : '鍚�' }}</ADescriptionsItem>
+            <ADescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.creatorTime || '-' }}</ADescriptionsItem>
+            <ADescriptionsItem label="澶囨敞" :span="2">{{ detail.remark || '-' }}</ADescriptionsItem>
+          </ADescriptions>
+        </a-card>
+
+        <a-card title="缁勫嵎鍐呭" :bordered="false" style="margin-top: 12px">
+          <div v-for="sec in detail.sections || []" :key="sec.id" class="section-block">
+            <div class="section-title">
+              {{ sec.sectionName }}
+              <span class="muted">锛坽{ labelOfType(sec.questionType) }} 路 {{ (sec.questions || []).length }} 棰橈級</span>
+            </div>
+            <a-table
+              size="small"
+              row-key="questionId"
+              :pagination="false"
+              :data-source="sec.questions || []"
+              :columns="[
+                { title: '搴忓彿', width: 60, customRender: ({ index }: any) => index + 1 },
+                { title: '缂栧彿', dataIndex: 'questionNo', width: 90 },
+                {
+                  title: '棰樺共',
+                  dataIndex: 'stem',
+                  customRender: ({ record }: any) => stripHtml(record.stem),
+                },
+                { title: '棰樺簱', dataIndex: 'bankName', width: 140 },
+                { title: '鍒嗗��', dataIndex: 'score', width: 80 },
+              ]"
+            />
+          </div>
+          <a-empty v-if="!(detail.sections || []).length" description="鏆傛棤缁勫嵎鍐呭" />
+        </a-card>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.tms-paper-detail-page {
+  height: 100%;
+  min-height: 0;
+}
+
+.tms-paper-detail-wrap {
+  height: 100%;
+  min-height: 0;
+  overflow-x: hidden;
+  overflow-y: auto !important;
+  background: #fff;
+  padding: 16px;
+  box-sizing: border-box;
+}
+
+.section-block {
+  margin-bottom: 16px;
+}
+.section-title {
+  margin-bottom: 8px;
+  font-weight: 600;
+}
+.muted {
+  margin-left: 8px;
+  color: #999;
+  font-weight: 400;
+  font-size: 13px;
+}
+</style>

--
Gitblit v1.8.0