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/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, '&').replace(/</g, '<').replace(/>/g, '>');
+}
+
+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