From 93c133349a5ccd7a328371fa113dce69d5611f21 Mon Sep 17 00:00:00 2001
From: liuyu <yu.liu@cqgbkj.com>
Date: 星期二, 22 九月 2026 09:25:39 +0800
Subject: [PATCH] feat(tms): 完成课程考试自测等页面与接口对接

---
 apps/jnpf-web-apps-main/src/views/x/tms/examScore/index.vue |   98 ++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 85 insertions(+), 13 deletions(-)

diff --git a/apps/jnpf-web-apps-main/src/views/x/tms/examScore/index.vue b/apps/jnpf-web-apps-main/src/views/x/tms/examScore/index.vue
index 341ed2c..bca3273 100644
--- a/apps/jnpf-web-apps-main/src/views/x/tms/examScore/index.vue
+++ b/apps/jnpf-web-apps-main/src/views/x/tms/examScore/index.vue
@@ -1,23 +1,28 @@
 <script lang="ts" setup>
 import type { ActionItem, BasicColumn } from '@jnpf/ui/vxeTable';
 
-import type { ExamScoreSummaryItem } from './types';
+import type { ExamScoreDetailRow, ExamScoreSummaryItem } from './types';
 
+import { ref } from 'vue';
 import { useRouter } from 'vue-router';
 
 import { useMessage } from '@jnpf/hooks';
 import { BasicVxeTable, TableAction, useVxeTable } from '@jnpf/ui/vxeTable';
 
-import { getExamScoreList } from '#/api/x/tms/examScore';
+import { exportExamScores, getExamScoreList } from '#/api/x/tms/examScore';
+import { TMS_BTN } from '#/views/x/tms/shared/ui';
+
+import '#/views/x/tms/shared/page.css';
 
 defineOptions({ name: 'TmsExamScore' });
 
 const router = useRouter();
 const { createMessage } = useMessage();
+const exporting = ref(false);
 
 const columns: BasicColumn[] = [
-  { title: '鍩硅涓婚', dataIndex: 'taskSubject', minWidth: 240 },
-  { title: '鍩硅缂栧彿', dataIndex: 'taskNo', width: 140 },
+  { title: '璇曞嵎鍚嶇О', dataIndex: 'taskSubject', minWidth: 240 },
+  { title: '璇曞嵎缂栧彿', dataIndex: 'taskNo', width: 180 },
   {
     title: '璇曞嵎鍚嶇О',
     dataIndex: 'paperName',
@@ -63,9 +68,9 @@
       },
       {
         field: 'taskNo',
-        label: '鍩硅缂栧彿',
+        label: '璇曞嵎缂栧彿',
         component: 'Input',
-        componentProps: { placeholder: '璇疯緭鍏ュ煿璁紪鍙�', submitOnPressEnter: true },
+        componentProps: { placeholder: '璇疯緭鍏ヨ瘯鍗风紪鍙�', submitOnPressEnter: true },
       },
     ],
   },
@@ -78,24 +83,90 @@
 });
 
 async function fetchList(params: Record<string, any>) {
-  return { data: await getExamScoreList(params) };
+  const page = await getExamScoreList(params);
+  return {
+    data: {
+      list: Array.isArray(page?.list) ? page.list : [],
+      pagination: page?.pagination || { total: 0 },
+    },
+  };
 }
 
 function handleDetail(record: ExamScoreSummaryItem) {
   router.push(`/tms/examScore/detail/${record.id}`);
 }
 
-function handleExport() {
-  const rows = getSelectRows?.() || [];
-  if (!rows.length) {
+async function handleExport() {
+  const selected = (getSelectRows?.() || []) as ExamScoreSummaryItem[];
+  if (!selected.length) {
     createMessage.warning('璇峰厛鍕鹃�夎瀵煎嚭鐨勮�冭瘯璁板綍');
     return;
   }
-  createMessage.success(`宸查�夋嫨 ${rows.length} 鏉★紙瀵煎嚭鎺ュ彛鑱旇皟鍚庣敓鏁堬級`);
+  if (exporting.value) return;
+  exporting.value = true;
+  try {
+    const rows = await exportExamScores(selected.map((item) => item.id));
+    if (!rows?.length) {
+      createMessage.warning('鎵�閫夎瘯鍗锋殏鏃犲凡浜ゅ嵎鎴愮哗');
+      return;
+    }
+    downloadScoreFile(rows, selected);
+    createMessage.success(`宸插鍑� ${rows.length} 鏉¤�冪敓鎴愮哗`);
+  } catch (e: any) {
+    createMessage.error(e?.message || '瀵煎嚭澶辫触');
+  } finally {
+    exporting.value = false;
+  }
+}
+
+function passText(flag?: string) {
+  if (flag === '1') return '鍚堟牸';
+  if (flag === '0') return '涓嶅悎鏍�';
+  return '-';
+}
+
+function xmlCell(value: unknown) {
+  const text = value == null ? '' : String(value);
+  return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+}
+
+function scoreFileName(selected: ExamScoreSummaryItem[]) {
+  const names = [...new Set(selected.map((item) => (item.taskSubject || '').trim()).filter(Boolean))];
+  const raw = names.length === 1 ? `鑰冭瘯鎴愮哗_${names[0]}` : '鑰冭瘯鎴愮哗';
+  return `${raw.replace(/[\\/:*?"<>|\r\n]/g, '_')}.xls`;
+}
+
+function downloadScoreFile(rows: ExamScoreDetailRow[], selected: ExamScoreSummaryItem[]) {
+  const header = ['璇曞嵎鍚嶇О', '璇曞嵎缂栧彿', '鑰冪敓', '鑰冭瘯寮�濮嬫椂闂�', '鑰冭瘯缁撴潫鏃堕棿', '鎴愮哗', '鍙婃牸鍒�', '鏄惁鍚堟牸'];
+  const body = rows.map((row) => [
+    row.taskSubject,
+    row.taskNo,
+    row.userName || row.userId,
+    row.examStart || '',
+    row.examEnd || '',
+    row.score,
+    row.passScore,
+    passText(row.passFlag),
+  ]);
+  const widths = [180, 160, 100, 150, 150, 60, 60, 80];
+  const cols = widths.map((width) => `<Column ss:Width="${width}"/>`).join('');
+  const toRow = (cells: unknown[]) =>
+    `<Row>${cells.map((cell) => `<Cell><Data ss:Type="String">${xmlCell(cell)}</Data></Cell>`).join('')}</Row>`;
+  const xml = `<?xml version="1.0" encoding="UTF-8"?>
+<?mso-application progid="Excel.Sheet"?>
+<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
+<Worksheet ss:Name="鑰冭瘯鎴愮哗"><Table>${cols}${toRow(header)}${body.map(toRow).join('')}</Table></Worksheet>
+</Workbook>`;
+  const blob = new Blob([xml], { type: 'application/vnd.ms-excel;charset=utf-8' });
+  const link = document.createElement('a');
+  link.href = URL.createObjectURL(blob);
+  link.download = scoreFileName(selected);
+  link.click();
+  URL.revokeObjectURL(link.href);
 }
 
 function getTableActions(record: ExamScoreSummaryItem): ActionItem[] {
-  return [{ label: '鑰冭瘯璇︽儏', onClick: handleDetail.bind(null, record) }];
+  return [{ label: TMS_BTN.detail, onClick: handleDetail.bind(null, record) }];
 }
 </script>
 
@@ -106,7 +177,8 @@
         <BasicVxeTable @register="registerTable">
           <template #tableTitle>
             <a-space>
-              <a-button type="primary" @click="handleExport">瀵煎嚭鑰冭瘯</a-button>
+              <a-button type="primary" :loading="exporting" @click="handleExport">{{ TMS_BTN.export }}</a-button>
+              <span class="tms-toolbar-hint">鎸夊満娆℃眹鎬绘垚缁╋紝鍙鍑烘垨杩涘叆鏄庣粏</span>
             </a-space>
           </template>
           <template #paperName="{ record }">

--
Gitblit v1.8.0