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

diff --git a/apps/jnpf-web-apps-main/src/views/x/tms/paper/index.vue b/apps/jnpf-web-apps-main/src/views/x/tms/paper/index.vue
new file mode 100644
index 0000000..971f7bd
--- /dev/null
+++ b/apps/jnpf-web-apps-main/src/views/x/tms/paper/index.vue
@@ -0,0 +1,256 @@
+<script lang="ts" setup>
+import type { ActionItem, BasicColumn } from '@jnpf/ui/vxeTable';
+
+import type { PaperEntity } from './types';
+
+import { computed, onMounted, ref } from 'vue';
+import { useRouter } from 'vue-router';
+
+import { useMessage } from '@jnpf/hooks';
+import { BasicVxeTable, TableAction, useVxeTable } from '@jnpf/ui/vxeTable';
+
+import { getPaperList, invalidatePaper, setPaperStatus } from '#/api/x/tms/paper';
+import { TMS_DIC_FIELD_NAMES } from '#/views/x/tms/shared/dic';
+
+import { STATUS_OPTIONS, labelOfSortMode, labelOfStatus, loadPaperDics } from './constants';
+
+defineOptions({ name: 'TmsPaperList' });
+
+const router = useRouter();
+const { createMessage } = useMessage();
+const invalidMode = ref(false);
+
+const activeStatusOptions = computed(() =>
+  STATUS_OPTIONS.value.filter((x) => (x.enCode || x.id) !== 'invalid'),
+);
+
+const columns: BasicColumn[] = [
+  { title: '璇曞嵎缂栧彿', dataIndex: 'paperNo', width: 120 },
+  { title: '璇曞嵎鍚嶇О', dataIndex: 'paperName', minWidth: 200 },
+  {
+    title: '鐘舵��',
+    dataIndex: 'bizStatus',
+    width: 100,
+    align: 'center',
+    slots: { default: 'bizStatus' },
+  },
+  {
+    title: '鏃堕暱(鍒嗛挓)',
+    dataIndex: 'durationMin',
+    width: 100,
+    align: 'center',
+  },
+  {
+    title: '鎬诲垎',
+    dataIndex: 'totalScore',
+    width: 90,
+    align: 'center',
+  },
+  {
+    title: '鍚堟牸鍒�',
+    dataIndex: 'passScore',
+    width: 90,
+    align: 'center',
+  },
+  {
+    title: '棰橀噺',
+    dataIndex: 'questionCount',
+    width: 80,
+    align: 'center',
+  },
+  {
+    title: '璇曢鎺掑簭',
+    dataIndex: 'sortMode',
+    width: 110,
+    customRender: ({ record }) => labelOfSortMode((record as PaperEntity).sortMode),
+  },
+  { title: '鍒涘缓鏃堕棿', dataIndex: 'creatorTime', width: 170 },
+];
+
+const [registerTable, { reload, getForm }] = useVxeTable({
+  api: fetchList,
+  columns,
+  immediate: false,
+  rowKey: 'id',
+  useSearchForm: true,
+  formConfig: {
+    baseColProps: { span: 6 },
+    compact: true,
+    schemas: [
+      {
+        field: 'keyword',
+        label: '鍏抽敭璇�',
+        component: 'Input',
+        componentProps: { placeholder: '缂栧彿/鍚嶇О', submitOnPressEnter: true },
+      },
+      {
+        field: 'bizStatus',
+        label: '鐘舵��',
+        component: 'Select',
+        componentProps: {
+          allowClear: true,
+          placeholder: '璇烽�夋嫨',
+          options: STATUS_OPTIONS.value,
+          fieldNames: TMS_DIC_FIELD_NAMES,
+        },
+      },
+    ],
+  },
+  actionColumn: {
+    width: 180,
+    title: '鎿嶄綔',
+    dataIndex: 'action',
+    fixed: 'right',
+  },
+});
+
+onMounted(async () => {
+  await loadPaperDics();
+  getForm()?.updateSchema?.([
+    {
+      field: 'bizStatus',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇烽�夋嫨',
+        options: activeStatusOptions.value,
+        fieldNames: TMS_DIC_FIELD_NAMES,
+      },
+    },
+  ]);
+  reload();
+});
+
+async function fetchList(params: Record<string, any>) {
+  const query = { ...params };
+  if (invalidMode.value) {
+    query.bizStatus = 'invalid';
+  }
+  const page = await getPaperList(query);
+  return {
+    data: {
+      list: Array.isArray(page?.list) ? page.list : [],
+      pagination: page?.pagination || { total: 0 },
+    },
+  };
+}
+
+function openInvalidList() {
+  invalidMode.value = true;
+  getForm()?.setFieldsValue?.({ bizStatus: undefined });
+  getForm()?.updateSchema?.([{ field: 'bizStatus', ifShow: false }]);
+  reload();
+}
+
+function backToNormal() {
+  invalidMode.value = false;
+  getForm()?.updateSchema?.([{ field: 'bizStatus', ifShow: true }]);
+  reload();
+}
+
+function statusColor(status?: string) {
+  if (status === 'open') return '#52c41a';
+  if (status === 'closed') return '#ff4d4f';
+  if (status === 'invalid') return '#999';
+  return undefined;
+}
+
+function handleCreate() {
+  router.push('/tms/paper/create');
+}
+
+function handleEdit(record: PaperEntity) {
+  if (record.bizStatus === 'open') {
+    createMessage.warning('璇峰厛鍋滅敤鍚庡啀缂栬緫');
+    return;
+  }
+  if (record.bizStatus === 'invalid') {
+    createMessage.warning('宸插簾寮冭瘯鍗蜂笉鍙紪杈�');
+    return;
+  }
+  router.push(`/tms/paper/edit/${record.id}`);
+}
+
+function handleDetail(record: PaperEntity) {
+  router.push(`/tms/paper/detail/${record.id}`);
+}
+
+async function handleInvalidate(record: PaperEntity) {
+  await invalidatePaper(record.id!);
+  createMessage.success('搴熷純鎴愬姛');
+  reload();
+}
+
+async function handleEnable(record: PaperEntity) {
+  await setPaperStatus(record.id!, 'open');
+  createMessage.success('宸插惎鐢�');
+  reload();
+}
+
+async function handleDisable(record: PaperEntity) {
+  await setPaperStatus(record.id!, 'closed');
+  createMessage.success('宸插仠鐢�');
+  reload();
+}
+
+function getTableActions(record: PaperEntity): ActionItem[] {
+  if (record.bizStatus === 'open') {
+    return [
+      {
+        label: '鍋滅敤',
+        modelConfirm: {
+          content: `纭畾鍋滅敤璇曞嵎銆�${record.paperName}銆嶅悧锛熷仠鐢ㄥ悗涓嶅彲鐢ㄤ簬鍩硅浠诲姟鍦ㄧ嚎鑰冭瘯銆俙,
+          onOk: handleDisable.bind(null, record),
+        },
+      },
+      { label: '璇︽儏', onClick: handleDetail.bind(null, record) },
+    ];
+  }
+  if (record.bizStatus === 'closed') {
+    return [
+      {
+        label: '鍚敤',
+        modelConfirm: {
+          content: `纭畾鍚敤璇曞嵎銆�${record.paperName}銆嶅悧锛熷惎鐢ㄥ悗鍙敤浜庡煿璁换鍔″湪绾胯�冭瘯銆俙,
+          onOk: handleEnable.bind(null, record),
+        },
+      },
+      { label: '缂栬緫', onClick: handleEdit.bind(null, record) },
+      {
+        label: '搴熷純',
+        color: 'error',
+        modelConfirm: {
+          content: `纭畾搴熷純璇曞嵎銆�${record.paperName}銆嶅悧锛熷簾寮冨悗涓嶅彲鍐嶅惎鐢紝涓斾笉鍙敤浜庡煿璁换鍔″湪绾胯�冭瘯銆俙,
+          onOk: handleInvalidate.bind(null, record),
+        },
+      },
+    ];
+  }
+  return [{ label: '璇︽儏', onClick: handleDetail.bind(null, record) }];
+}
+</script>
+
+<template>
+  <div class="jnpf-content-wrapper">
+    <div class="jnpf-content-wrapper-center">
+      <div class="jnpf-content-wrapper-content">
+        <BasicVxeTable @register="registerTable">
+          <template #tableTitle>
+            <a-space>
+              <template v-if="!invalidMode">
+                <a-button type="primary" pre-icon="icon-ym icon-ym-btn-add" @click="handleCreate">鏂板</a-button>
+                <a-button @click="openInvalidList">搴熷純鍒楄〃</a-button>
+              </template>
+              <a-button v-else @click="backToNormal">杩斿洖</a-button>
+            </a-space>
+          </template>
+          <template #bizStatus="{ record }">
+            <span :style="{ color: statusColor(record.bizStatus) }">{{ labelOfStatus(record.bizStatus) }}</span>
+          </template>
+          <template #action="{ record }">
+            <TableAction :actions="getTableActions(record)" />
+          </template>
+        </BasicVxeTable>
+      </div>
+    </div>
+  </div>
+</template>

--
Gitblit v1.8.0