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

diff --git a/apps/jnpf-web-apps-main/src/views/x/tms/task/index.vue b/apps/jnpf-web-apps-main/src/views/x/tms/task/index.vue
new file mode 100644
index 0000000..042086f
--- /dev/null
+++ b/apps/jnpf-web-apps-main/src/views/x/tms/task/index.vue
@@ -0,0 +1,325 @@
+<script lang="ts" setup>
+import type { ActionItem, BasicColumn } from '@jnpf/ui/vxeTable';
+
+import type { TaskEntity } from './types';
+
+import { onMounted } from 'vue';
+import { useRouter } from 'vue-router';
+
+import { useMessage } from '@jnpf/hooks';
+import { BasicVxeTable, TableAction, useVxeTable } from '@jnpf/ui/vxeTable';
+
+import { Modal } from 'ant-design-vue';
+
+import {
+  cancelTask,
+  cancelTaskBatch,
+  deleteTask,
+  getTaskList,
+  publishTask,
+  restoreTask,
+} from '#/api/x/tms/task';
+import { TMS_DIC_FIELD_NAMES } from '#/views/x/tms/shared/dic';
+import { TMS_BTN } from '#/views/x/tms/shared/ui';
+
+import {
+  PUBLISH_STATUS_OPTIONS,
+  SOURCE_TYPE_OPTIONS,
+  TRAIN_MODE_OPTIONS,
+  labelOfPublishStatus,
+  labelOfSourceType,
+  labelOfTrainMode,
+  loadTaskDics,
+  publishStatusColor,
+} from './constants';
+
+defineOptions({ name: 'TmsTaskList' });
+
+const router = useRouter();
+const { createMessage } = useMessage();
+
+const columns: BasicColumn[] = [
+  { title: '鍩硅缂栧彿', dataIndex: 'taskNo', width: 140 },
+  { title: '鍩硅涓婚', dataIndex: 'subject', minWidth: 200 },
+  {
+    title: '鏉ユ簮绫诲瀷',
+    dataIndex: 'sourceType',
+    width: 110,
+    customRender: ({ record }) => labelOfSourceType((record as TaskEntity).sourceType),
+  },
+  {
+    title: '鍩硅鏂瑰紡',
+    dataIndex: 'trainMode',
+    width: 110,
+    customRender: ({ record }) => labelOfTrainMode((record as TaskEntity).trainMode),
+  },
+  { title: '寮�濮嬫椂闂�', dataIndex: 'startTime', width: 170 },
+  { title: '缁撴潫鏃堕棿', dataIndex: 'endTime', width: 170 },
+  { title: '鍦扮偣', dataIndex: 'placeName', width: 140 },
+  {
+    title: '鐘舵��',
+    dataIndex: 'publishStatus',
+    width: 100,
+    align: 'center',
+    slots: { default: 'publishStatus' },
+  },
+  { title: '鍒涘缓鏃堕棿', dataIndex: 'creatorTime', width: 170 },
+];
+
+const [registerTable, { reload, getForm, getSelectRows }] = 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: 'publishStatus',
+        label: '鐘舵��',
+        component: 'Select',
+        componentProps: {
+          allowClear: true,
+          placeholder: '璇烽�夋嫨',
+          options: PUBLISH_STATUS_OPTIONS.value,
+          fieldNames: TMS_DIC_FIELD_NAMES,
+        },
+      },
+      {
+        field: 'trainMode',
+        label: '鍩硅鏂瑰紡',
+        component: 'Select',
+        componentProps: {
+          allowClear: true,
+          placeholder: '璇烽�夋嫨',
+          options: TRAIN_MODE_OPTIONS.value,
+          fieldNames: TMS_DIC_FIELD_NAMES,
+        },
+      },
+      {
+        field: 'sourceType',
+        label: '鏉ユ簮绫诲瀷',
+        component: 'Select',
+        componentProps: {
+          allowClear: true,
+          placeholder: '璇烽�夋嫨',
+          options: SOURCE_TYPE_OPTIONS.value,
+          fieldNames: TMS_DIC_FIELD_NAMES,
+        },
+      },
+      {
+        field: 'startTimeRange',
+        label: '寮�濮嬫椂闂�',
+        component: 'DateRange',
+        componentProps: {
+          format: 'YYYY-MM-DD',
+          placeholder: ['寮�濮�', '缁撴潫'],
+        },
+      },
+    ],
+  },
+  rowSelection: { type: 'checkbox' },
+  actionColumn: {
+    width: 260,
+    title: '鎿嶄綔',
+    dataIndex: 'action',
+    fixed: 'right',
+  },
+});
+
+onMounted(async () => {
+  await loadTaskDics();
+  getForm()?.updateSchema?.([
+    {
+      field: 'publishStatus',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇烽�夋嫨',
+        options: PUBLISH_STATUS_OPTIONS.value,
+        fieldNames: TMS_DIC_FIELD_NAMES,
+      },
+    },
+    {
+      field: 'trainMode',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇烽�夋嫨',
+        options: TRAIN_MODE_OPTIONS.value,
+        fieldNames: TMS_DIC_FIELD_NAMES,
+      },
+    },
+    {
+      field: 'sourceType',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇烽�夋嫨',
+        options: SOURCE_TYPE_OPTIONS.value,
+        fieldNames: TMS_DIC_FIELD_NAMES,
+      },
+    },
+  ]);
+  reload();
+});
+
+async function fetchList(params: Record<string, any>) {
+  const query = { ...params };
+  const range = query.startTimeRange;
+  if (Array.isArray(range) && range.length === 2) {
+    query.startTimeFrom = range[0];
+    query.startTimeTo = range[1];
+  }
+  delete query.startTimeRange;
+  const page = await getTaskList(query);
+  return {
+    data: {
+      list: Array.isArray(page?.list) ? page.list : [],
+      pagination: page?.pagination || { total: 0 },
+    },
+  };
+}
+
+function handleCreate() {
+  router.push('/tms/task/create');
+}
+
+function handleEdit(record: TaskEntity) {
+  if (record.publishStatus !== 'draft') {
+    createMessage.warning('浠呮湭鍙戝竷浠诲姟鍙淮鎶�');
+    return;
+  }
+  router.push(`/tms/task/edit/${record.id}`);
+}
+
+function handleDetail(record: TaskEntity) {
+  router.push(`/tms/task/detail/${record.id}`);
+}
+
+async function handlePublish(record: TaskEntity) {
+  await publishTask(record.id!);
+  createMessage.success('鍙戝竷鎴愬姛');
+  reload();
+}
+
+async function handleCancel(record: TaskEntity) {
+  await cancelTask(record.id!);
+  createMessage.success('宸插彇娑�');
+  reload();
+}
+
+async function handleRestore(record: TaskEntity) {
+  await restoreTask(record.id!);
+  createMessage.success('宸叉仮澶嶄负鑽夌');
+  reload();
+}
+
+async function handleDelete(record: TaskEntity) {
+  await deleteTask(record.id!);
+  createMessage.success('鍒犻櫎鎴愬姛');
+  reload();
+}
+
+async function handleBatchCancel() {
+  const rows = (getSelectRows?.() || []) as TaskEntity[];
+  const ids = rows.filter((r) => r.publishStatus === 'published').map((r) => r.id!).filter(Boolean);
+  if (!ids.length) {
+    createMessage.warning('璇峰嬀閫夊凡鍙戝竷鐨勪换鍔�');
+    return;
+  }
+  Modal.confirm({
+    title: '鎵归噺鍙栨秷',
+    content: `纭畾鍙栨秷閫変腑鐨� ${ids.length} 涓凡鍙戝竷浠诲姟锛熷凡鏈夌鍒颁汉鍛樼殑浠诲姟灏嗗け璐ャ�俙,
+    onOk: async () => {
+      await cancelTaskBatch(ids);
+      createMessage.success('鎵归噺鍙栨秷鎴愬姛');
+      reload();
+    },
+  });
+}
+
+function getTableActions(record: TaskEntity): ActionItem[] {
+  const actions: ActionItem[] = [{ label: TMS_BTN.detail, onClick: handleDetail.bind(null, record) }];
+  if (record.publishStatus === 'draft') {
+    actions.unshift(
+      {
+        label: TMS_BTN.publish,
+        modelConfirm: {
+          content: `纭畾鍙戝竷銆�${record.subject}銆嶏紵灏嗙敓鎴愪釜浜轰换鍔′笌鍩硅璁板綍銆俙,
+          onOk: handlePublish.bind(null, record),
+        },
+      },
+      {
+        label: TMS_BTN.maintain,
+        onClick: handleEdit.bind(null, record),
+      },
+      {
+        label: TMS_BTN.delete,
+        color: 'error',
+        modelConfirm: {
+          content: `纭畾鍒犻櫎銆�${record.subject}銆嶅悧锛焋,
+          onOk: handleDelete.bind(null, record),
+        },
+      },
+    );
+  } else if (record.publishStatus === 'published') {
+    actions.unshift({
+      label: TMS_BTN.cancel,
+      color: 'error',
+      modelConfirm: {
+        content: `纭畾鍙栨秷銆�${record.subject}銆嶏紵宸叉湁绛惧埌浜哄憳鏃朵笉鍙彇娑堛�俙,
+        onOk: handleCancel.bind(null, record),
+      },
+    });
+  } else if (record.publishStatus === 'cancelled') {
+    actions.unshift({
+      label: TMS_BTN.restore,
+      modelConfirm: {
+        content: `纭畾灏嗐��${record.subject}銆嶆仮澶嶄负鑽夌锛焋,
+        onOk: handleRestore.bind(null, record),
+      },
+    });
+  }
+  return actions;
+}
+</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>
+              <a-button type="primary" pre-icon="icon-ym icon-ym-btn-add" @click="handleCreate">
+                {{ TMS_BTN.add }}涓存椂鍩硅
+              </a-button>
+              <a-button danger @click="handleBatchCancel">鎵归噺{{ TMS_BTN.cancel }}</a-button>
+            </a-space>
+          </template>
+          <template #publishStatus="{ record }">
+            <a-tag :color="publishStatusColor(record.publishStatus)">
+              {{ labelOfPublishStatus(record.publishStatus) }}
+            </a-tag>
+          </template>
+          <template #action="{ record }">
+            <TableAction :actions="getTableActions(record)" />
+          </template>
+        </BasicVxeTable>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.tms-task-list-hint {
+  color: #999;
+  font-size: 12px;
+}
+</style>

--
Gitblit v1.8.0