Browse Source

feat: api-打回管理

zhangjie 1 week ago
parent
commit
65ba9b5ab2

+ 2 - 1
.eslintignore

@@ -1,3 +1,4 @@
 /*.json
 /*.js
-dist
+dist
+components.d.ts

+ 1 - 0
.prettierignore

@@ -2,6 +2,7 @@
 .local
 .output.js
 /node_modules/**
+components.d.ts
 
 **/*.svg
 **/*.sh

+ 66 - 66
components.d.ts

@@ -5,76 +5,76 @@
 // Read more: https://github.com/vuejs/core/pull/3399
 import '@vue/runtime-core'
 
-export {};
+export {}
 
 declare module '@vue/runtime-core' {
   export interface GlobalComponents {
-    Chart: typeof import('./src/components/chart/index.vue')['default'];
-    Cropper: typeof import('./src/components/select-img-area/Cropper.vue')['default'];
-    ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'];
-    ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'];
-    ElButton: typeof import('element-plus/es')['ElButton'];
-    ElCheckbox: typeof import('element-plus/es')['ElCheckbox'];
-    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'];
-    ElCol: typeof import('element-plus/es')['ElCol'];
-    ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'];
-    ElDatePicker: typeof import('element-plus/es')['ElDatePicker'];
-    ElDescriptions: typeof import('element-plus/es')['ElDescriptions'];
-    ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'];
-    ElDialog: typeof import('element-plus/es')['ElDialog'];
-    ElDivider: typeof import('element-plus/es')['ElDivider'];
-    ElDropdown: typeof import('element-plus/es')['ElDropdown'];
-    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'];
-    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'];
-    ElForm: typeof import('element-plus/es')['ElForm'];
-    ElFormItem: typeof import('element-plus/es')['ElFormItem'];
-    ElIcon: typeof import('element-plus/es')['ElIcon'];
-    ElImageViewer: typeof import('element-plus/es')['ElImageViewer'];
-    ElInput: typeof import('element-plus/es')['ElInput'];
-    ElInputNumber: typeof import('element-plus/es')['ElInputNumber'];
-    ElLink: typeof import('element-plus/es')['ElLink'];
-    ElMenu: typeof import('element-plus/es')['ElMenu'];
-    ElMenuItem: typeof import('element-plus/es')['ElMenuItem'];
-    ElOption: typeof import('element-plus/es')['ElOption'];
-    ElPagination: typeof import('element-plus/es')['ElPagination'];
-    ElProgress: typeof import('element-plus/es')['ElProgress'];
-    ElRadio: typeof import('element-plus/es')['ElRadio'];
-    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'];
-    ElResult: typeof import('element-plus/es')['ElResult'];
-    ElRow: typeof import('element-plus/es')['ElRow'];
-    ElSelect: typeof import('element-plus/es')['ElSelect'];
-    ElSpace: typeof import('element-plus/es')['ElSpace'];
-    ElSubMenu: typeof import('element-plus/es')['ElSubMenu'];
-    ElSwitch: typeof import('element-plus/es')['ElSwitch'];
-    ElTable: typeof import('element-plus/es')['ElTable'];
-    ElTableColumn: typeof import('element-plus/es')['ElTableColumn'];
-    ElTabPane: typeof import('element-plus/es')['ElTabPane'];
-    ElTabs: typeof import('element-plus/es')['ElTabs'];
-    ElTag: typeof import('element-plus/es')['ElTag'];
-    ElTextarea: typeof import('element-plus/es')['ElTextarea'];
-    ElTooltip: typeof import('element-plus/es')['ElTooltip'];
-    ElUpload: typeof import('element-plus/es')['ElUpload'];
-    FileUpload: typeof import('./src/components/file-upload/index.vue')['default'];
-    Footer: typeof import('./src/components/footer/index.vue')['default'];
-    ImportDialog: typeof import('./src/components/import-dialog/index.vue')['default'];
-    PageBreadcrumb: typeof import('./src/components/page-breadcrumb/index.vue')['default'];
-    RouterLink: typeof import('vue-router')['RouterLink'];
-    RouterView: typeof import('vue-router')['RouterView'];
-    SelectClass: typeof import('./src/components/select-class/index.vue')['default'];
-    SelectCollege: typeof import('./src/components/select-college/index.vue')['default'];
-    SelectCourse: typeof import('./src/components/select-course/index.vue')['default'];
-    SelectExam: typeof import('./src/components/select-exam/index.vue')['default'];
-    SelectImgArea: typeof import('./src/components/select-img-area/index.vue')['default'];
-    SelectOption: typeof import('./src/components/select-option/index.vue')['default'];
-    SelectRangeDatetime: typeof import('./src/components/select-range-datetime/index.vue')['default'];
-    SelectRangeTime: typeof import('./src/components/select-range-time/index.vue')['default'];
-    SelectSubject: typeof import('./src/components/select-subject/index.vue')['default'];
-    SelectTeaching: typeof import('./src/components/select-teaching/index.vue')['default'];
-    SvgIcon: typeof import('./src/components/svg-icon/index.vue')['default'];
-    TableField: typeof import('./src/components/table-field/index.vue')['default'];
-    UploadButton: typeof import('./src/components/upload-button/index.vue')['default'];
+    Chart: typeof import('./src/components/chart/index.vue')['default']
+    Cropper: typeof import('./src/components/select-img-area/Cropper.vue')['default']
+    ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
+    ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
+    ElButton: typeof import('element-plus/es')['ElButton']
+    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
+    ElCol: typeof import('element-plus/es')['ElCol']
+    ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
+    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
+    ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
+    ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
+    ElDialog: typeof import('element-plus/es')['ElDialog']
+    ElDivider: typeof import('element-plus/es')['ElDivider']
+    ElDropdown: typeof import('element-plus/es')['ElDropdown']
+    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
+    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
+    ElForm: typeof import('element-plus/es')['ElForm']
+    ElFormItem: typeof import('element-plus/es')['ElFormItem']
+    ElIcon: typeof import('element-plus/es')['ElIcon']
+    ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
+    ElInput: typeof import('element-plus/es')['ElInput']
+    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
+    ElLink: typeof import('element-plus/es')['ElLink']
+    ElMenu: typeof import('element-plus/es')['ElMenu']
+    ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
+    ElOption: typeof import('element-plus/es')['ElOption']
+    ElPagination: typeof import('element-plus/es')['ElPagination']
+    ElProgress: typeof import('element-plus/es')['ElProgress']
+    ElRadio: typeof import('element-plus/es')['ElRadio']
+    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
+    ElResult: typeof import('element-plus/es')['ElResult']
+    ElRow: typeof import('element-plus/es')['ElRow']
+    ElSelect: typeof import('element-plus/es')['ElSelect']
+    ElSpace: typeof import('element-plus/es')['ElSpace']
+    ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
+    ElSwitch: typeof import('element-plus/es')['ElSwitch']
+    ElTable: typeof import('element-plus/es')['ElTable']
+    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+    ElTabPane: typeof import('element-plus/es')['ElTabPane']
+    ElTabs: typeof import('element-plus/es')['ElTabs']
+    ElTag: typeof import('element-plus/es')['ElTag']
+    ElTextarea: typeof import('element-plus/es')['ElTextarea']
+    ElTooltip: typeof import('element-plus/es')['ElTooltip']
+    ElUpload: typeof import('element-plus/es')['ElUpload']
+    FileUpload: typeof import('./src/components/file-upload/index.vue')['default']
+    Footer: typeof import('./src/components/footer/index.vue')['default']
+    ImportDialog: typeof import('./src/components/import-dialog/index.vue')['default']
+    PageBreadcrumb: typeof import('./src/components/page-breadcrumb/index.vue')['default']
+    RouterLink: typeof import('vue-router')['RouterLink']
+    RouterView: typeof import('vue-router')['RouterView']
+    SelectClass: typeof import('./src/components/select-class/index.vue')['default']
+    SelectCollege: typeof import('./src/components/select-college/index.vue')['default']
+    SelectCourse: typeof import('./src/components/select-course/index.vue')['default']
+    SelectExam: typeof import('./src/components/select-exam/index.vue')['default']
+    SelectImgArea: typeof import('./src/components/select-img-area/index.vue')['default']
+    SelectOption: typeof import('./src/components/select-option/index.vue')['default']
+    SelectRangeDatetime: typeof import('./src/components/select-range-datetime/index.vue')['default']
+    SelectRangeTime: typeof import('./src/components/select-range-time/index.vue')['default']
+    SelectSubject: typeof import('./src/components/select-subject/index.vue')['default']
+    SelectTeaching: typeof import('./src/components/select-teaching/index.vue')['default']
+    SvgIcon: typeof import('./src/components/svg-icon/index.vue')['default']
+    TableField: typeof import('./src/components/table-field/index.vue')['default']
+    UploadButton: typeof import('./src/components/upload-button/index.vue')['default']
   }
   export interface ComponentCustomProperties {
-    vLoading: typeof import('element-plus/es')['ElLoadingDirective'];
+    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
   }
 }

+ 31 - 4
src/api/reject.ts

@@ -1,8 +1,10 @@
-import axios from 'axios';
+import axios, { AxiosResponse } from 'axios';
 import {
   RejectListPageParams,
+  RejectListFilter,
   RejectListPageRes,
   RejectStatisticsListParams,
+  RejectStatisticsFilter,
   RejectStatisticsListRes,
   RejectTypeItem,
   RejectTypeUpdateParam,
@@ -12,22 +14,47 @@ import {
 export function getRejectList(
   params: RejectListPageParams
 ): Promise<RejectListPageRes> {
-  return axios.post('/api/student/list', {}, { params });
+  return axios.post('/api/admin/exam/reject/list/history', {}, { params });
 }
 // 获取打回记录列表
 export function getRejectRecordList(
   params: RejectListPageParams
 ): Promise<RejectListPageRes> {
-  return axios.post('/api/student/list', {}, { params });
+  return axios.post('/api/admin/exam/reject/list/history', {}, { params });
+}
+
+// 导出打回记录列表
+export function exportRejectRecord(
+  params: RejectListFilter
+): Promise<AxiosResponse<Blob>> {
+  return axios.post(
+    '/api/admin/exam/reject/list/history/export',
+    {},
+    { params, responseType: 'blob' }
+  );
 }
 
 // 获取打回统计列表
 export function getRejectStatisticsList(
   params: RejectStatisticsListParams
 ): Promise<RejectStatisticsListRes> {
-  return axios.post('/api/student/list', {}, { params });
+  const url = params.showGroupNumber
+    ? '/api/admin/exam/reject/list/group'
+    : '/api/admin/exam/reject/list/subject';
+  return axios.post(url, {}, { params });
+}
+
+// 打回统计列表导出
+export function exportRejectStat(
+  params: RejectStatisticsFilter
+): Promise<AxiosResponse<Blob>> {
+  const url = params.showGroupNumber
+    ? '/api/admin/exam/reject/list/group/export'
+    : '/api/admin/exam/reject/list/subject/export';
+  return axios.post(url, {}, { params, responseType: 'blob' });
 }
 
+// 打回类型 ------------->
 // 查询打回类型
 export function getRejectTypeList(examId: number): Promise<RejectTypeItem[]> {
   return axios.post(

+ 24 - 18
src/api/types/reject.ts

@@ -3,21 +3,24 @@ import { PageResult, PageParams } from './common';
 export interface RejectItem {
   id: number;
   /** 科目 */
-  subject: string;
+  subjectName: string;
+  subjectCode: string;
   /** 分组序号 */
-  groupNo: number;
+  groupNumber: number;
+  groupText: string;
+  groupName: string;
   /** 准考证号 */
-  examNo: string;
+  examNumber: string;
   /** 密号 */
-  secretNo: string;
+  secretNumber: string;
   /** 打回原因 */
   rejectReason: string;
   /** 评卷员 */
-  marker: string;
+  markerName: string;
   /** 给分明细 */
-  scoreDetails: string;
+  scoreList: string;
   /** 打回人 */
-  rejectBy: string;
+  headerName: string;
   /** 打回时间 */
   rejectTime: number;
 }
@@ -25,40 +28,43 @@ export type RejectListPageRes = PageResult<RejectItem>;
 
 export interface RejectListFilter {
   /** 科目 */
-  subject: string | null;
+  subjectCode: string | null;
   /** 分组序号 */
-  groupNo: number;
+  groupNumber: number;
   /** 打回原因 */
   rejectReason: string;
   /** 评卷员 */
-  marker: string;
+  markerId: string | null;
   /** 打回人 */
-  rejectBy: string;
+  headerId: string | null;
 }
 export type RejectListPageParams = PageParams<RejectListFilter>;
 
 // 打回统计
 export interface RejectStatisticsItem {
   /** 科目 */
-  subject: string;
+  subjectText: string;
+  subjectCode: string;
   /** 分组序号 */
-  groupNo: number;
+  groupNumber: number;
+  groupText: string;
+  groupName: string;
   /** 任务总数 */
-  totalTasks: number;
+  totalCount: number;
   /** 已完成数量 */
-  completedTasks: number;
+  finishCount: number;
   /** 打回数量 */
   rejectCount: number;
   /** 打回率 */
-  rejectRate: number;
+  rejectRatio: number;
 }
 export type RejectStatisticsListRes = PageResult<RejectStatisticsItem>;
 
 export interface RejectStatisticsFilter {
   /** 科目 */
-  subject: string | null;
+  subjectCode: string | null;
   /** 显示分组 */
-  showGroupNo: boolean;
+  showGroupNumber: boolean;
 }
 export type RejectStatisticsListParams = PageParams<RejectStatisticsFilter>;
 

+ 6 - 0
src/utils/download-export.ts

@@ -21,6 +21,7 @@ import {
   downloadSubjectObjectiveImportTemplate,
 } from '@/api/subject';
 import { exportScanStatByPoint, exportScanStatByCourse } from '@/api/scan';
+import { exportRejectStat, exportRejectRecord } from '@/api/reject';
 
 import useLoading from '@/hooks/loading';
 
@@ -66,6 +67,11 @@ const downloadConfig = {
   exportScanStatByPoint,
   // 按科目导出
   exportScanStatByCourse,
+  // reject
+  // 导出打回统计
+  exportRejectStat,
+  // 导出打回记录
+  exportRejectRecord,
 };
 type DownloadType = keyof typeof downloadConfig;
 

+ 24 - 16
src/views/reject/RejectManage.vue

@@ -2,11 +2,11 @@
   <div class="part-box is-filter">
     <el-form inline>
       <el-form-item label="科目">
-        <select-subject v-model="searchModel.subject"></select-subject>
+        <select-subject v-model="searchModel.subjectCode"></select-subject>
       </el-form-item>
       <el-form-item label="分组">
         <el-input-number
-          v-model.number="searchModel.groupNo"
+          v-model.number="searchModel.groupNumber"
           :min="1"
           :max="999"
           :step="1"
@@ -29,7 +29,7 @@
       </el-form-item>
       <el-form-item label="评卷员">
         <el-input
-          v-model.trim="searchModel.marker"
+          v-model.trim="searchModel.markerId"
           placeholder="请输入"
           clearable
           style="width: 120px"
@@ -38,7 +38,7 @@
       </el-form-item>
       <el-form-item label="打回人">
         <el-input
-          v-model.trim="searchModel.rejectBy"
+          v-model.trim="searchModel.headerId"
           placeholder="请输入"
           clearable
           style="width: 120px"
@@ -59,24 +59,27 @@
       stripe
     >
       <el-table-column type="index" label="序号" width="60" />
-      <el-table-column prop="subject" label="科目" min-width="100" />
-      <el-table-column prop="groupNo" label="分组序号" width="100" />
-      <el-table-column prop="examNo" label="准考证号" width="120" />
-      <el-table-column prop="secretNo" label="密号" width="100" />
+      <el-table-column prop="subjectName" label="科目" min-width="100" />
+      <el-table-column prop="groupNumber" label="分组序号" width="100" />
+      <el-table-column prop="examNumber" label="准考证号" width="120" />
+      <el-table-column prop="secretNumber" label="密号" width="100" />
       <el-table-column prop="rejectReason" label="打回原因" min-width="150" />
-      <el-table-column prop="marker" label="评卷员" width="100" />
-      <el-table-column prop="scoreDetails" label="给分明细" min-width="120" />
-      <el-table-column prop="rejectBy" label="打回人" width="100" />
+      <el-table-column prop="markerName" label="评卷员" width="100" />
+      <el-table-column prop="scoreList" label="给分明细" min-width="120" />
+      <el-table-column prop="headerName" label="打回人" width="100" />
       <el-table-column label="打回时间" width="180">
         <template #default="scope">
           {{ timestampFilter(scope.row.rejectTime) }}
         </template>
       </el-table-column>
-      <el-table-column label="操作" width="100" fixed="right">
+      <el-table-column label="操作" width="140" fixed="right">
         <template #default="scope">
           <el-button type="primary" link @click="onViewDetail(scope.row)">
             查看详情
           </el-button>
+          <el-button type="primary" link @click="onAssignTask(scope.row)">
+            任务指定</el-button
+          >
         </template>
       </el-table-column>
     </el-table>
@@ -103,11 +106,11 @@
   });
 
   const searchModel = reactive<RejectListFilter>({
-    subject: null,
-    groupNo: undefined,
+    subjectCode: null,
+    groupNumber: undefined,
     rejectReason: '',
-    marker: '',
-    rejectBy: '',
+    markerId: null,
+    headerId: null,
   });
 
   const { dataList, pagination, loading, toPage, pageSizeChange } =
@@ -117,4 +120,9 @@
     // TODO: 实现查看详情功能
     console.log('查看详情:', row);
   }
+
+  function onAssignTask(row: RejectItem) {
+    // TODO: 实现任务指定功能;等评卷管理做完
+    console.log('任务指定:', row);
+  }
 </script>

+ 18 - 18
src/views/reject/RejectRecord.vue

@@ -2,11 +2,11 @@
   <div class="part-box is-filter">
     <el-form inline>
       <el-form-item label="科目">
-        <select-subject v-model="searchModel.subject"></select-subject>
+        <select-subject v-model="searchModel.subjectCode"></select-subject>
       </el-form-item>
       <el-form-item label="分组">
         <el-input-number
-          v-model.number="searchModel.groupNo"
+          v-model.number="searchModel.groupNumber"
           :min="1"
           :max="999"
           :step="1"
@@ -29,7 +29,7 @@
       </el-form-item>
       <el-form-item label="评卷员">
         <el-input
-          v-model.trim="searchModel.marker"
+          v-model.trim="searchModel.markerId"
           placeholder="请输入"
           clearable
           style="width: 120px"
@@ -38,7 +38,7 @@
       </el-form-item>
       <el-form-item label="打回人">
         <el-input
-          v-model.trim="searchModel.rejectBy"
+          v-model.trim="searchModel.headerId"
           placeholder="请输入"
           clearable
           style="width: 120px"
@@ -60,14 +60,14 @@
       stripe
     >
       <el-table-column type="index" label="序号" width="60" />
-      <el-table-column prop="subject" label="科目" min-width="100" />
-      <el-table-column prop="groupNo" label="分组序号" width="100" />
-      <el-table-column prop="examNo" label="准考证号" width="120" />
-      <el-table-column prop="secretNo" label="密号" width="100" />
+      <el-table-column prop="subjectName" label="科目" min-width="100" />
+      <el-table-column prop="groupNumber" label="分组序号" width="100" />
+      <el-table-column prop="examNumber" label="准考证号" width="120" />
+      <el-table-column prop="secretNumber" label="密号" width="100" />
       <el-table-column prop="rejectReason" label="打回原因" min-width="150" />
-      <el-table-column prop="marker" label="评卷员" width="100" />
-      <el-table-column prop="scoreDetails" label="给分明细" min-width="120" />
-      <el-table-column prop="rejectBy" label="打回人" width="100" />
+      <el-table-column prop="markerName" label="评卷员" width="100" />
+      <el-table-column prop="scoreList" label="给分明细" min-width="120" />
+      <el-table-column prop="headerName" label="打回人" width="100" />
       <el-table-column label="打回时间" width="180">
         <template #default="scope">
           {{ timestampFilter(scope.row.rejectTime) }}
@@ -91,24 +91,24 @@
   import { RejectItem, RejectListFilter } from '@/api/types/reject';
   import useTable from '@/hooks/table';
   import { timestampFilter } from '@/utils/filter';
+  import { downloadExport } from '@/utils/download-export';
 
   defineOptions({
     name: 'RejectRecord',
   });
 
   const searchModel = reactive<RejectListFilter>({
-    subject: null,
-    groupNo: undefined,
+    subjectCode: null,
+    groupNumber: undefined,
     rejectReason: '',
-    marker: '',
-    rejectBy: '',
+    markerId: null,
+    headerId: null,
   });
 
   const { dataList, pagination, loading, toPage, pageSizeChange } =
     useTable<RejectItem>(getRejectRecordList, searchModel, false);
 
-  function exportData() {
-    // TODO: 实现导出功能
-    console.log('导出问题卷数据');
+  async function exportData() {
+    await downloadExport('exportRejectRecord', searchModel);
   }
 </script>

+ 21 - 14
src/views/reject/RejectStatistics.vue

@@ -2,10 +2,12 @@
   <div class="part-box is-filter">
     <el-form inline>
       <el-form-item label="科目">
-        <select-subject v-model="searchModel.subject"></select-subject>
+        <select-subject v-model="searchModel.subjectCode"></select-subject>
       </el-form-item>
       <el-form-item>
-        <el-checkbox v-model="searchModel.showGroupNo">显示分组</el-checkbox>
+        <el-checkbox v-model="searchModel.showGroupNumber"
+          >显示分组</el-checkbox
+        >
       </el-form-item>
       <el-form-item>
         <el-button type="primary" @click="toPage(1)">查询</el-button>
@@ -22,21 +24,26 @@
       stripe
       @sort-change="handleSortChange"
     >
-      <el-table-column prop="subject" label="科目" min-width="200" sortable />
       <el-table-column
-        v-if="searchModel.showGroupNo"
-        prop="groupNo"
+        prop="subjectText"
+        label="科目"
+        min-width="200"
+        sortable
+      />
+      <el-table-column
+        v-if="searchModel.showGroupNumber"
+        prop="groupNumber"
         label="分组"
         min-width="100"
       />
       <el-table-column
-        prop="totalTasks"
+        prop="totalCount"
         label="任务总数"
         min-width="100"
         sortable
       />
       <el-table-column
-        prop="completedTasks"
+        prop="finishCount"
         label="已完成"
         min-width="100"
         sortable
@@ -48,13 +55,13 @@
         sortable
       />
       <el-table-column
-        prop="rejectRate"
+        prop="rejectRatio"
         label="打回率"
         min-width="100"
         sortable
       >
         <template #default="scope">
-          {{ formatPercentage(scope.row.rejectRate) }}
+          {{ formatPercentage(scope.row.rejectRatio) }}
         </template>
       </el-table-column>
     </el-table>
@@ -77,14 +84,15 @@
     RejectStatisticsFilter,
   } from '@/api/types/reject';
   import useTable from '@/hooks/table';
+  import { downloadExport } from '@/utils/download-export';
 
   defineOptions({
     name: 'RejectStatistics',
   });
 
   const searchModel = reactive<RejectStatisticsFilter>({
-    subject: null,
-    showGroupNo: false,
+    subjectCode: null,
+    showGroupNumber: false,
   });
 
   const {
@@ -105,8 +113,7 @@
     return `${(rate * 100).toFixed(1)}%`;
   }
 
-  function exportData() {
-    // TODO: 实现导出功能
-    console.log('导出打回统计数据');
+  async function exportData() {
+    await downloadExport('exportRejectStat', searchModel);
   }
 </script>