Parcourir la source

feat: api-课程管理

zhangjie il y a 3 jours
Parent
commit
6640821867
40 fichiers modifiés avec 480 ajouts et 466 suppressions
  1. 1 0
      .gitignore
  2. 1 0
      components.d.ts
  3. 1 1
      src/api/base.ts
  4. 5 5
      src/api/mark.ts
  5. 1 1
      src/api/order.ts
  6. 1 1
      src/api/review.ts
  7. 63 35
      src/api/subject.ts
  8. 1 1
      src/api/types/analysis.ts
  9. 4 4
      src/api/types/check.ts
  10. 1 1
      src/api/types/issue-paper.ts
  11. 9 9
      src/api/types/mark.ts
  12. 2 2
      src/api/types/reject.ts
  13. 2 2
      src/api/types/review.ts
  14. 1 1
      src/api/types/scan.ts
  15. 1 1
      src/api/types/score.ts
  16. 39 37
      src/api/types/subject.ts
  17. 1 1
      src/api/user.ts
  18. 6 3
      src/assets/style/base.scss
  19. 2 4
      src/components/index.ts
  20. 0 84
      src/components/select-college/index.vue
  21. 27 26
      src/components/select-option/index.vue
  22. 62 0
      src/components/select-option/search.ts
  23. 3 3
      src/components/select-subject/index.vue
  24. 3 3
      src/router/routes/modules/base.ts
  25. 10 0
      src/utils/download-export.ts
  26. 2 3
      src/views/log/LogManage.vue
  27. 6 6
      src/views/mark/components/SetTrialCountDialog.vue
  28. 2 2
      src/views/mark/group-edit/GroupEdit.vue
  29. 5 5
      src/views/mark/group-edit/GroupQuestionSelect.vue
  30. 2 2
      src/views/review/AllReview.vue
  31. 2 2
      src/views/review/ScoreReviewStatistics.vue
  32. 11 8
      src/views/student/StudentManage.vue
  33. 21 19
      src/views/subject/OptionalRuleEdit.vue
  34. 33 37
      src/views/subject/PaperStructEdit.vue
  35. 62 45
      src/views/subject/SubjectManage.vue
  36. 1 1
      src/views/subject/components/ModifyOptionalQuestionRule.vue
  37. 65 89
      src/views/subject/components/ModifyPaperStructure.vue
  38. 8 8
      src/views/subject/components/OptionalQuestionSelect.vue
  39. 5 6
      src/views/subject/components/OptionalRuleDialog.vue
  40. 8 8
      src/views/subject/components/SubjectSettingDialog.vue

+ 1 - 0
.gitignore

@@ -3,6 +3,7 @@ node_modules
 dist
 dist-ssr
 *.local
+components.d.ts
 
 # Log files
 *.log*

+ 1 - 0
components.d.ts

@@ -65,6 +65,7 @@ declare module '@vue/runtime-core' {
     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'];

+ 1 - 1
src/api/base.ts

@@ -9,7 +9,7 @@ export function examQuery(): Promise<ExamQueryItem[]> {
 }
 // 通用查询-科目
 export function subjectQuery(examId: number): Promise<SubjectItem[]> {
-  return axios.post('/api/admin/subject/query', {}, { params: { examId } });
+  return axios.post('/api/admin/subject/list', {}, { params: { examId } });
 }
 // 通用查询-学院
 export function collegeQuery(): Promise<string[]> {

+ 5 - 5
src/api/mark.ts

@@ -149,25 +149,25 @@ export function markGroupUpdate(
 
 // 设置试评数量
 export function markGroupSetTrialCount({
-  subjectId,
+  subjectCode,
   count,
 }: {
-  subjectId: number;
+  subjectCode: string;
   count: number;
 }): Promise<boolean> {
   return axios.post(
     '/api/mark/group/set-trial-count',
     {},
-    { params: { subjectId, count } }
+    { params: { subjectCode, count } }
   );
 }
 
 // 数据校对
-export function markGroupDataCheck(subjectId: number) {
+export function markGroupDataCheck(subjectCode: string) {
   return axios.post(
     '/api/mark/group/data-check',
     {},
-    { params: { subjectId } }
+    { params: { subjectCode } }
   );
 }
 

+ 1 - 1
src/api/order.ts

@@ -95,7 +95,7 @@ export function studentImportListPage(
 }
 // 考生信息导入-导入模板下载
 export function studentInfoTemplate(): Promise<AxiosResponse<Blob>> {
-  return axios.get('/api/admin/std/import/template', {
+  return axios.post('/api/admin/std/import/template', {
     responseType: 'blob',
   });
 }

+ 1 - 1
src/api/review.ts

@@ -11,7 +11,7 @@ import {
 // 复核管理
 // 获取复核进度统计信息
 export function getReviewStatInfo(): Promise<ReviewStatInfo> {
-  return axios.get('/api/review/stat/info');
+  return axios.post('/api/review/stat/info');
 }
 
 // 获取复核进度列表信息

+ 63 - 35
src/api/subject.ts

@@ -1,4 +1,4 @@
-import axios from 'axios';
+import axios, { AxiosResponse } from 'axios';
 import {
   SubjectTotalScoreStatItem,
   SubjectListPageParam,
@@ -15,60 +15,88 @@ import {
 
 // 科目管理
 // 获取科目总分统计列表
-export function getSubjectTotalScoreStatList(
-  examId: number
-): Promise<SubjectTotalScoreStatItem[]> {
-  return axios.get(`/api/subject/total-score-stat/${examId}`);
+export function getSubjectTotalScoreStatList(): Promise<
+  SubjectTotalScoreStatItem[]
+> {
+  return axios.post(`/api/admin/subject/statistic`);
 }
 
 // 获取科目列表
 export function getSubjectList(
   params: SubjectListPageParam
 ): Promise<SubjectListPageRes> {
-  return axios.post('/api/subject/list', {}, { params });
+  return axios.post('/api/admin/subject/query', {}, { params });
 }
 // 获取科目详情
-export function getSubjectDetail(subjectId: number): Promise<SubjectItem> {
-  return axios.post('/api/subject/list', {}, { params: { subjectId } });
+export function getSubjectDetail(subjectCode: string): Promise<SubjectItem> {
+  return axios.post(
+    '/api/admin/subject/find',
+    {},
+    { params: { code: subjectCode } }
+  );
 }
 
 // 科目分析计算
-export function subjectAnalysis(
-  examId: number,
-  subjectId?: number
-): Promise<boolean> {
-  return axios.post('/api/subject/analysis', { params: { examId, subjectId } });
+export function subjectAnalysis(subjectCode?: string): Promise<boolean> {
+  return axios.post('/api/admin/subject/report', {
+    params: { subjectCode },
+  });
 }
 
 // 客观题统分
-export function subjectObjectiveMarkScore(examId: number): Promise<boolean> {
+export function subjectObjectiveMarkScore(
+  subjectCode?: string
+): Promise<boolean> {
   return axios.post(
-    '/api/subject/objective-stat',
+    '/api/admin/subject/calculate',
     {},
     {
-      params: { examId },
+      params: { subjectCode },
     }
   );
 }
 
-// 获取科目试卷结构列表
-export function getPaperStructureList(
-  subjectId: number
-): Promise<PaperStructureListPageRes> {
-  return axios.post(`/api/subject/paper-structure/${subjectId}`);
-}
-
-// 获取科目设置信息
-export function getSubjectSetting(
-  subjectId: number
-): Promise<SubjectSettingInfo> {
-  return axios.post('/api/subject/setting', {}, { params: { subjectId } });
-}
 // 保存科目设置信息
 export function saveSubjectSetting(
   data: SubjectSettingInfo
 ): Promise<SubjectSettingInfo> {
-  return axios.post('/api/subject/setting', data);
+  return axios.post('/api/admin/subject/update', data);
+}
+
+// 导出主客观题
+export function exportSubjectMainObjective(
+  objective: boolean
+): Promise<AxiosResponse<Blob>> {
+  return axios.post(
+    '/api/admin/subject/export',
+    {},
+    {
+      responseType: 'blob',
+      params: { objective },
+    }
+  );
+}
+
+// 下载主客观题导入模版
+export function downloadSubjectObjectiveImportTemplate(params: {
+  objective: boolean;
+}): Promise<AxiosResponse<Blob>> {
+  return axios.post(
+    '/api/admin/subject/template',
+    {},
+    {
+      responseType: 'blob',
+      params,
+    }
+  );
+}
+
+// 试卷结构 -------------->
+// 获取科目试卷结构列表
+export function getPaperStructureList(
+  subjectCode: string
+): Promise<PaperStructureListPageRes> {
+  return axios.post(`/api/subject/paper-structure/${subjectCode}`);
 }
 
 // 新增/编辑试卷结构
@@ -89,29 +117,29 @@ export function deletePaperStructures(ids: number[]): Promise<boolean> {
   );
 }
 
-// 选做题设置
+// 选做题设置  ---------------->
 // 获取选做题试题列表
 export function getOptionalQuestionList(
-  subjectId: number
+  subjectCode: string
 ): Promise<OptionalQuestionItem[]> {
   return axios.post(
     '/api/subject/optional-question/list',
     {},
     {
-      params: { subjectId },
+      params: { subjectCode },
     }
   );
 }
 
 // 获取选做题规则列表
 export function getOptionalQuestionRuleList(
-  subjectId: number
+  subjectCode: string
 ): Promise<OptionalRuleItem[]> {
   return axios.post(
     '/api/subject/optional-question/rule/list',
     {},
     {
-      params: { subjectId },
+      params: { subjectCode },
     }
   );
 }

+ 1 - 1
src/api/types/analysis.ts

@@ -2,7 +2,7 @@ import { PageResult, PageParams } from './common';
 
 export interface AnalysisListFilter {
   // 科目
-  subject: number | null;
+  subject: string | null;
 }
 export type AnalysisListPageParam = PageParams<AnalysisListFilter>;
 

+ 4 - 4
src/api/types/check.ts

@@ -38,7 +38,7 @@ export interface ImageCheckDataListFilter {
   // 学号
   studentNo: string;
   // 科目
-  subject: number | null;
+  subject: string | null;
 }
 export type ImageCheckPageParams = PageParams<ImageCheckDataListFilter>;
 
@@ -53,7 +53,7 @@ export interface ImageCheckDataListItem {
   // 学号
   studentNo: string;
   // 科目
-  subject: number | null;
+  subject: string | null;
   // 扫描批次
   scanBatch: string;
   // 上传时间
@@ -66,7 +66,7 @@ export interface ManualConfirmDataListFilter {
   // 确认类型
   confirmType: number | null;
   // 科目
-  subject: number | null;
+  subject: string | null;
   // 考点
   examPoint: string;
 }
@@ -83,7 +83,7 @@ export interface ManualConfirmDataListItem {
   // 学号
   studentNo: string;
   // 科目
-  subject: number | null;
+  subject: string | null;
   // 考点
   examPoint: string;
   // 扫描批次

+ 1 - 1
src/api/types/issue-paper.ts

@@ -31,7 +31,7 @@ export type IssuePaperListPageRes = PageResult<IssuePaperItem>;
 //
 export interface IssuePaperListFilter {
   // 科目
-  subject: number | null;
+  subject: string | null;
   // 分组序号
   groupNo: number | undefined;
   // 问题类型

+ 9 - 9
src/api/types/mark.ts

@@ -35,7 +35,7 @@ export type MarkQualityMonitorListPageRes = PageResult<MarkQualityMonitorItem>;
 
 export interface MarkQualityMonitorListFilter {
   // 科目
-  subject?: number | null;
+  subject: string | null;
   // 分组
   group?: string;
   // 已评卷
@@ -57,7 +57,7 @@ export interface QMScoreItem {
 }
 export type QMScoreListParam = {
   // 科目
-  subject: number | null;
+  subject: string | null;
   // 分组
   group: string;
 };
@@ -84,7 +84,7 @@ export interface MarkArbitrationItem {
 export type MarkArbitrationListPageRes = PageResult<MarkArbitrationItem>;
 export interface MarkArbitrationListFilter {
   // 科目
-  subject?: number | null;
+  subject: string | null;
   // 分组
   group?: string;
   // 状态
@@ -130,7 +130,7 @@ export interface MarkTaskItem {
 export type MarkTaskListPageRes = PageResult<MarkTaskItem>;
 export interface MarkTaskListFilter {
   // 科目
-  subject?: number | null;
+  subject: string | null;
   // 分组
   group?: string;
   // 状态
@@ -184,7 +184,7 @@ export interface MarkTrialItem {
 export type MarkTrialListPageRes = PageResult<MarkTrialItem>;
 export interface MarkTrialListFilter {
   // 科目
-  subject?: number | null;
+  subject: string | null;
   // 分组
   group?: string;
   // 准考证号
@@ -224,7 +224,7 @@ export interface MarkMarkerListFilter {
   // 姓名
   name?: string;
   // 科目
-  subject?: number | null;
+  subject: string | null;
   // 分组
   group?: string;
   // 已评卷
@@ -266,14 +266,14 @@ export interface MarkGroupItem {
 export type MarkGroupListPageRes = MarkGroupItem[];
 export interface MarkGroupListFilter {
   // 科目
-  subject?: number | null;
+  subject: string | null;
 }
 export type MarkGroupListPageParam = PageParams<MarkGroupListFilter>;
 
 export interface MarkGroupUpdateParams {
   // id
   id?: number;
-  subjectId: number;
+  subjectCode: string;
   // 分组序号
   groupNo: number;
   // 名称
@@ -340,7 +340,7 @@ export interface MarkStatItem {
 export type MarkStatListPageRes = PageResult<MarkStatItem>;
 export interface MarkStatListFilter {
   // 科目
-  subject?: number | null;
+  subject: string | null;
   // 完成进度
   progress?: string;
 }

+ 2 - 2
src/api/types/reject.ts

@@ -25,7 +25,7 @@ export type RejectListPageRes = PageResult<RejectItem>;
 
 export interface RejectListFilter {
   /** 科目 */
-  subject: number | null;
+  subject: string | null;
   /** 分组序号 */
   groupNo: number;
   /** 打回原因 */
@@ -56,7 +56,7 @@ export type RejectStatisticsListRes = PageResult<RejectStatisticsItem>;
 
 export interface RejectStatisticsFilter {
   /** 科目 */
-  subject: number | null;
+  subject: string | null;
   /** 显示分组 */
   showGroupNo: boolean;
 }

+ 2 - 2
src/api/types/review.ts

@@ -35,7 +35,7 @@ export type ReviewStatListPageRes = PageResult<ReviewStatItem>;
 
 export interface ReviewStatListFilter {
   // 科目
-  subjectId: number | null;
+  subjectCode: string | null;
   // 选做科目
   isOptional: boolean | null;
   // 完成进度
@@ -67,7 +67,7 @@ export type FullReviewListPageRes = PageResult<FullReviewItem>;
 
 export interface FullReviewListFilter {
   // 科目
-  subjectId?: number;
+  subjectCode: string;
   // 选做异常
   isOptionalException?: boolean;
   // 状态:已复核、未复核

+ 1 - 1
src/api/types/scan.ts

@@ -19,7 +19,7 @@ export type ScanListPageRes = PageResult<ScanItem>;
 
 export interface ScanCourseListFilter {
   // 科目
-  subject?: number | null;
+  subject: string | null;
   // 层次
   level?: string;
   // 专业类型

+ 1 - 1
src/api/types/score.ts

@@ -48,7 +48,7 @@ export interface ScoreListFilter {
   // 学号
   studentNo: string;
   // 科目
-  subject: number | null;
+  subject: string | null;
   // 筛选类型
   filterType: string;
   // 是否缺考

+ 39 - 37
src/api/types/subject.ts

@@ -18,37 +18,39 @@ export interface SubjectItem {
   // 层次
   level: string;
   // 专业类型
-  majorType: string;
+  category: string;
   // 选做科目
-  optional: boolean;
+  selective: boolean;
   // 试卷
   paperUrl: string;
   // 答案
   answerUrl: string;
   // 试卷类型
   paperType: string;
-  // 卡格式
-  cardFormatType: string;
+  // 卡格式: JSON:电子题卡,ZIP:扫描题卡
+  cardType: 'ZIP' | 'JSON';
   // 客观总分
-  objectiveTotalScore: number;
+  objectiveScore: number;
   // 主观总分
-  subjectiveTotalScore: number;
+  subjectiveScore: number;
   // 试卷总分
-  paperTotalScore: number;
+  totalScore: number;
   // 状态
-  status: string;
+  locked: boolean;
+  // 备注
+  remark: string;
 }
 export type SubjectListPageRes = PageResult<SubjectItem>;
 
 export interface SubjectListFilter {
-  // 科目名称
-  subject?: number | null;
+  // 科目
+  code?: string | null;
   // 层次
   level?: string;
   // 专业类型
-  majorType?: string;
+  category?: string;
   // 选做科目
-  optional?: boolean;
+  selective?: boolean;
   // 状态
   status: string;
   // 总分
@@ -64,7 +66,7 @@ export interface SubjectSettingInfo {
   // 科目代码
   code: string;
   // 评卷显示题目昵称
-  showQuestionNickname: boolean;
+  displayQuestionName: boolean;
   // 及格分
   passScore: number;
   // 优秀分
@@ -72,9 +74,9 @@ export interface SubjectSettingInfo {
   // 原图遮盖
   sheetConfig: CoverArea[] | null;
   // 评卷提交自动定位
-  autoLocate: boolean;
+  autoScroll: boolean;
   // 自动对切题卡
-  autoCut: boolean;
+  enableSplit: boolean;
 }
 
 // 试卷结构列表:试卷类型	大题名称	大题昵称	大题号	小题号	满分	给分次数 是否客观题	间隔分 题型	答案	判分策略	分组
@@ -83,29 +85,29 @@ export interface PaperStructureItem {
   // 试卷类型
   paperType: string;
   // 大题名称
-  bigQuestionName: string;
+  mainTitle: string;
   // 大题昵称
-  bigQuestionNickname: string;
+  name: string;
   // 大题号
-  bigQuestionNo: number;
+  mainNumber: number;
   // 小题号
-  smallQuestionNo: number;
+  subNumber: number;
   // 满分
-  fullScore: number;
+  totalScore: number;
   // 给分次数
-  giveScoreCount: number;
+  trackCount: number;
   // 是否客观题
-  isObjective: boolean;
+  objective: boolean;
   // 间隔分
   intervalScore: number;
   // 题型
-  questionType: string;
+  type: string;
   // 答案
   answer: string;
   // 判分策略
-  scoringStrategy: string;
+  objectivePolicy: string;
   // 分组
-  grouping: string;
+  groupNumber: string;
 }
 export type PaperStructureListPageRes = PaperStructureItem[];
 
@@ -115,36 +117,36 @@ export type PaperStructureUpdateParams = Partial<PaperStructureItem>;
 export interface OptionalQuestionItem {
   id: number;
   // 大题名称
-  bigQuestionName: string;
+  mainTitle: string;
   // 大题号
-  bigQuestionNo: number;
+  mainNumber: number;
   // 小题号
-  smallQuestionNo: number;
+  subNumber: number;
   // 大题昵称
-  bigQuestionNickname: string;
+  name: string;
   // 满分
-  fullScore: number;
+  totalScore: number;
   // 间隔分
   intervalScore: number;
   // 选做题
-  optional: boolean;
+  selective: boolean;
   // 选做题分组
-  optionalQuestionGroup: string;
+  selectiveIndex: string;
   // 选做题区
-  optionalQuestionArea: string;
+  selectivePart: string;
 }
 
 // 选做题规则列表:选做题组	规则	大题名称	分值
 export interface OptionalRuleItem {
   id: number;
-  subjectId: number;
+  subjectCode: string;
   // 选做题规则: 几选几
   ruleSelecCount: number;
   ruleTotalCount: number;
   // 取分规则
-  scoreRule: OptionalScoreRule;
+  scorePolicy: OptionalScoreRule;
   // 大题名称
-  bigQuestionName: string;
+  mainTitle: string;
   // 分值
   score: number;
   // 试题结构ids
@@ -157,7 +159,7 @@ export interface OptionalRuleUpdateParam {
   ruleSelecCount: number;
   ruleTotalCount: number;
   // 区分规则
-  scoreRule: OptionalScoreRule;
+  scorePolicy: OptionalScoreRule;
   // 试题结构ids
   questionStructureIds: number[];
 }

+ 1 - 1
src/api/user.ts

@@ -29,7 +29,7 @@ export function userLogout() {
 export function userListPage(
   params: UserListPageParam
 ): Promise<UserListPageRes> {
-  return axios.get('/api/admin/user/query', { params });
+  return axios.post('/api/admin/user/query', { params });
 }
 // 新增或编辑用户
 export function updateUser(data: UserUpdateParam): Promise<any> {

+ 6 - 3
src/assets/style/base.scss

@@ -24,6 +24,10 @@
     border: 1px solid var(--color-border);
     padding-bottom: 1px;
 
+    + .part-box {
+      border: 1px solid var(--color-border);
+    }
+
     .el-form-item {
       margin-bottom: 15px;
       margin-right: 16px;
@@ -32,9 +36,8 @@
       padding-right: 8px;
       color: var(--color-text-dark);
     }
-
-    + .part-box {
-      border: 1px solid var(--color-border);
+    .el-input {
+      width: 200px;
     }
   }
 

+ 2 - 4
src/components/index.ts

@@ -9,11 +9,10 @@ import SelectRangeTime from './select-range-time/index.vue';
 import UploadButton from './upload-button/index.vue';
 import SelectExam from './select-exam/index.vue';
 import SelectSubject from './select-subject/index.vue';
-import SelectCollege from './select-college/index.vue';
-import SelectClass from './select-class/index.vue';
 import ImportDialog from './import-dialog/index.vue';
 import TableField from './table-field/index.vue';
 import PageBreadcrumb from './page-breadcrumb/index.vue';
+import SelectOption from './select-option/index.vue';
 
 export default {
   install(Vue: App) {
@@ -24,8 +23,7 @@ export default {
     Vue.component('SelectRangeTime', SelectRangeTime);
     Vue.component('UploadButton', UploadButton);
     Vue.component('SelectExam', SelectExam);
-    Vue.component('SelectCollege', SelectCollege);
-    Vue.component('SelectClass', SelectClass);
+    Vue.component('SelectOption', SelectOption);
     Vue.component('SelectSubject', SelectSubject);
     Vue.component('ImportDialog', ImportDialog);
     Vue.component('TableField', TableField);

+ 0 - 84
src/components/select-college/index.vue

@@ -1,84 +0,0 @@
-<template>
-  <el-select
-    v-model="selected"
-    :placeholder="props.placeholder"
-    :clearable="props.clearable"
-    :disabled="props.disabled"
-    filterable
-    default-first-option
-    v-bind="attrs"
-    @change="onChange"
-  >
-    <el-option
-      v-for="item in optionList"
-      :key="item.value"
-      :label="item.label"
-      :value="item.value"
-    />
-  </el-select>
-</template>
-
-<script setup lang="ts">
-  import { ref, useAttrs, watch } from 'vue';
-  import { collegeQuery } from '@/api/base';
-
-  defineOptions({
-    name: 'SelectCollege',
-  });
-
-  type ValueType = string | Array<string> | null;
-
-  const props = withDefaults(
-    defineProps<{
-      modelValue: ValueType;
-      clearable?: boolean;
-      disabled?: boolean;
-      placeholder?: string;
-      multiple?: boolean;
-    }>(),
-    {
-      clearable: true,
-      disabled: false,
-      placeholder: '请选择',
-      multiple: false,
-    }
-  );
-  const emit = defineEmits(['update:modelValue', 'change']);
-  const attrs = useAttrs();
-
-  interface OptionListItem {
-    value: string;
-    label: string;
-  }
-
-  const selected = ref<ValueType>();
-  const optionList = ref<OptionListItem[]>([]);
-  const search = async () => {
-    const res = await collegeQuery();
-    optionList.value = res.map((item) => {
-      return { value: item, label: item };
-    });
-  };
-  search(); // Initial load
-
-  const onChange = () => {
-    const selectedData = props.multiple
-      ? optionList.value.filter(
-          (item) =>
-            selected.value && (selected.value as string[]).includes(item.value)
-        )
-      : optionList.value.filter((item) => selected.value === item.value);
-    emit('update:modelValue', selected.value || null);
-    emit('change', props.multiple ? selectedData : selectedData[0]);
-  };
-
-  watch(
-    () => props.modelValue,
-    (val) => {
-      selected.value = val || undefined;
-    },
-    {
-      immediate: true,
-    }
-  );
-</script>

+ 27 - 26
src/components/select-class/index.vue → src/components/select-option/index.vue

@@ -6,6 +6,7 @@
     :disabled="props.disabled"
     filterable
     default-first-option
+    :multiple="false"
     v-bind="attrs"
     @change="onChange"
   >
@@ -20,10 +21,10 @@
 
 <script setup lang="ts">
   import { ref, useAttrs, watch } from 'vue';
-  import { classQuery } from '@/api/base';
+  import useSearch, { SelectType } from './search';
 
   defineOptions({
-    name: 'SelectClass',
+    name: 'SelectOption',
   });
 
   type ValueType = string | Array<string> | null;
@@ -34,44 +35,44 @@
       clearable?: boolean;
       disabled?: boolean;
       placeholder?: string;
-      multiple?: boolean;
+      type: SelectType;
+      params?: Record<string, any>;
     }>(),
     {
       clearable: true,
       disabled: false,
       placeholder: '请选择',
-      multiple: false,
     }
   );
   const emit = defineEmits(['update:modelValue', 'change']);
   const attrs = useAttrs();
 
-  interface OptionListItem {
-    value: string;
-    label: string;
-  }
-
-  const selected = ref<ValueType>();
-  const optionList = ref<OptionListItem[]>([]);
-  const search = async () => {
-    const res = await classQuery();
-    optionList.value = res.map((item) => {
-      return { value: item, label: item };
-    });
-  };
-  search(); // Initial load
+  const selected = ref<string | Array<string> | undefined>();
+  const { optionList, search } = useSearch(props.type);
+  search(props.params);
 
   const onChange = () => {
-    const selectedData = props.multiple
-      ? optionList.value.filter(
-          (item) =>
-            selected.value && (selected.value as string[]).includes(item.value)
-        )
-      : optionList.value.filter((item) => selected.value === item.value);
-    emit('update:modelValue', selected.value || null);
-    emit('change', props.multiple ? selectedData : selectedData[0]);
+    const selectedData = optionList.value.filter(
+      (item) => selected.value === item.value
+    );
+    emit('update:modelValue', selectedData[0].value || null);
+    emit('change', selectedData[0]);
   };
 
+  watch(
+    () => props.params,
+    (val, oldval) => {
+      if (!val) return;
+
+      if (JSON.stringify(val) !== JSON.stringify(oldval)) {
+        search(val);
+      }
+    },
+    {
+      deep: true,
+    }
+  );
+
   watch(
     () => props.modelValue,
     (val) => {

+ 62 - 0
src/components/select-option/search.ts

@@ -0,0 +1,62 @@
+import axios from 'axios';
+import { ref } from 'vue';
+
+import useLoading from '@/hooks/loading';
+
+const selectConfig = {
+  // 卷型下拉列表
+  subjectPaperType: '/api/admin/subject/getPaperType',
+  // 科目层次下拉列表
+  subjectLevel: '/api/admin/subject/level/list',
+  // 学院
+  college: '/api/admin/student/college/list',
+  // 班级
+  className: '/api/admin/student/className/list',
+  // 考点
+  examPoint: '/api/admin/student/examSite/list',
+  // 专业类型
+  subjectCategory: '/api/admin/student/category/list',
+};
+
+export type SelectType = keyof typeof selectConfig;
+
+export interface OptionListItem {
+  value: number;
+  label: string;
+}
+
+export default function useSearch(type: SelectType) {
+  const { loading, setLoading } = useLoading();
+  const optionList = ref<OptionListItem[]>([]);
+
+  async function search(params?: Record<string, any>) {
+    if (!selectConfig[type]) {
+      console.error('下拉列表类型错误!');
+      return;
+    }
+
+    try {
+      setLoading(true);
+      const res = (await axios.post(
+        selectConfig[type],
+        {},
+        { params }
+      )) as string[];
+
+      optionList.value = res.map((item) => ({
+        value: item,
+        label: item,
+      }));
+    } catch (error) {
+      console.error(error);
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  return {
+    loading,
+    optionList,
+    search,
+  };
+}

+ 3 - 3
src/components/select-subject/index.vue

@@ -28,7 +28,7 @@
   defineOptions({
     name: 'SelectSubject',
   });
-  type ValueType = number | Array<number> | null;
+  type ValueType = string | Array<string> | null;
 
   const props = withDefaults(
     defineProps<{
@@ -49,11 +49,11 @@
   const attrs = useAttrs();
 
   interface OptionListItem {
-    value: number;
+    value: string;
     label: string;
   }
 
-  const selected = ref<number | Array<number> | undefined>();
+  const selected = ref<string | Array<string> | undefined>();
   const optionList = ref<OptionListItem[]>([]);
   const search = async () => {
     if (!appStore.curExam?.id) return;

+ 3 - 3
src/router/routes/modules/base.ts

@@ -56,7 +56,7 @@ const BASE: AppRouteRecordRaw = {
       },
     },
     {
-      path: '/subject/paper-struct-edit/:subjectId',
+      path: '/subject/paper-struct-edit/:subjectCode',
       name: 'PaperStructEdit',
       component: () => import('@/views/subject/PaperStructEdit.vue'),
       meta: {
@@ -66,7 +66,7 @@ const BASE: AppRouteRecordRaw = {
       },
     },
     {
-      path: '/subject/optional-rule-edit/:subjectId',
+      path: '/subject/optional-rule-edit/:subjectCode',
       name: 'OptionalRuleEdit',
       component: () => import('@/views/subject/OptionalRuleEdit.vue'),
       meta: {
@@ -242,7 +242,7 @@ const BASE: AppRouteRecordRaw = {
         },
         {
           // 编辑分组
-          path: '/mark-manage/group/:subjectId/edit/:groupId?',
+          path: '/mark-manage/group/:subjectCode/edit/:groupId?',
           name: 'GroupEdit',
           component: () => import('@/views/mark/group-edit/GroupEdit.vue'),
           meta: {

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

@@ -16,6 +16,11 @@ import {
   exportStudent,
 } from '@/api/student';
 import { exportLog } from '@/api/log';
+import {
+  exportSubjectMainObjective,
+  downloadSubjectObjectiveImportTemplate,
+} from '@/api/subject';
+
 import useLoading from '@/hooks/loading';
 
 import { downloadByApi } from './download';
@@ -50,6 +55,11 @@ const downloadConfig = {
   // log
   // 导出日志
   exportLog,
+  // subject
+  // 导出主客观题
+  exportSubjectMainObjective,
+  // 下载主客观题导入模版
+  downloadSubjectObjectiveImportTemplate,
 };
 type DownloadType = keyof typeof downloadConfig;
 

+ 2 - 3
src/views/log/LogManage.vue

@@ -6,7 +6,6 @@
           v-model.trim="searchModel.loginName"
           placeholder="请输入"
           clearable
-          style="width: 120px"
         >
         </el-input>
       </el-form-item>
@@ -17,8 +16,8 @@
           clearable
           style="width: 120px"
         >
-          <el-option label="违纪" :value="true" />
-          <el-option label="正常" :value="false" />
+          <el-option label="评卷员" :value="true" />
+          <el-option label="管理员" :value="false" />
         </el-select>
       </el-form-item>
       <el-form-item label="操作类型">

+ 6 - 6
src/views/mark/components/SetTrialCountDialog.vue

@@ -56,7 +56,7 @@
   defineExpose({ open, close });
 
   interface Props {
-    subjectId?: number; // 科目ID
+    subjectCode: string; // 科目ID
   }
 
   const props = defineProps<Props>();
@@ -65,12 +65,12 @@
   const formRef = ref<FormInstance>();
 
   interface FormModel {
-    subjectId: number;
+    subjectCode: string;
     count: number;
   }
 
   const initialFormState: FormModel = {
-    subjectId: 0,
+    subjectCode: 0,
     count: 0,
   };
 
@@ -93,7 +93,7 @@
     const valid = await formRef.value?.validate().catch(() => false);
     if (!valid) return;
 
-    if (!formModel.subjectId) {
+    if (!formModel.subjectCode) {
       ElMessage.warning('请选择科目');
       return;
     }
@@ -113,8 +113,8 @@
 
   /* init modal */
   function modalBeforeOpen() {
-    if (props.subjectId) {
-      formModel.subjectId = props.subjectId;
+    if (props.subjectCode) {
+      formModel.subjectCode = props.subjectCode;
     }
   }
 </script>

+ 2 - 2
src/views/mark/group-edit/GroupEdit.vue

@@ -10,7 +10,7 @@
       <GroupQuestionSelect
         ref="questionSelectRef"
         v-model="ruleFormData.questions"
-        :subject-id="props.rowData?.subjectId"
+        :subject-id="props.rowData?.subjectCode"
       />
     </div>
     <!-- 第二步:设置规则 -->
@@ -92,7 +92,7 @@
   const getInitialRuleFormData = (): MarkGroupUpdateParams => {
     return {
       id: route.params?.groupId,
-      subjectId: route.params.subjectId,
+      subjectCode: route.params.subjectCode,
       groupNo: 0,
       name: '',
       markingArea: [],

+ 5 - 5
src/views/mark/group-edit/GroupQuestionSelect.vue

@@ -48,13 +48,13 @@
 
   interface Props {
     modelValue: MarkGroupUpdateParams['questions'];
-    subjectId: number;
+    subjectCode: string;
     disabledIds: string[];
   }
 
   const props = withDefaults(defineProps<Props>(), {
     modelValue: [],
-    subjectId: 0,
+    subjectCode: null,
     disabledIds: () => [],
   });
 
@@ -82,12 +82,12 @@
 
   // 获取试题列表
   const getList = async () => {
-    if (!props.subjectId) return;
+    if (!props.subjectCode) return;
 
     setLoading(true);
     try {
       // TODO:就当是获取选做题试题列表吧,后面依据实际情况修改
-      const res = await getOptionalQuestionList(props.subjectId);
+      const res = await getOptionalQuestionList(props.subjectCode);
       dataList.value = res || [];
 
       // 设置已选中的项
@@ -127,7 +127,7 @@
   });
 
   onMounted(() => {
-    if (props.subjectId) {
+    if (props.subjectCode) {
       getList();
     }
   });

+ 2 - 2
src/views/review/AllReview.vue

@@ -2,7 +2,7 @@
   <div class="part-box is-filter">
     <el-form inline>
       <el-form-item label="科目">
-        <select-subject v-model="searchModel.subjectId"></select-subject>
+        <select-subject v-model="searchModel.subjectCode"></select-subject>
       </el-form-item>
       <el-form-item label="选做科目">
         <el-select
@@ -265,7 +265,7 @@
   });
 
   const searchModel = reactive<FullReviewListFilter>({
-    subjectId: null,
+    subjectCode: null,
     isOptionalException: undefined,
     isReviewed: undefined,
     academyId: undefined,

+ 2 - 2
src/views/review/ScoreReviewStatistics.vue

@@ -16,7 +16,7 @@
   <div class="part-box is-border">
     <el-form inline>
       <el-form-item label="科目">
-        <select-subject v-model="searchModel.subjectId"></select-subject>
+        <select-subject v-model="searchModel.subjectCode"></select-subject>
       </el-form-item>
       <el-form-item label="选做科目">
         <el-select
@@ -128,7 +128,7 @@
   });
 
   const searchModel = reactive<ReviewStatListFilter>({
-    subjectId: null,
+    subjectCode: null,
     isOptional: null,
     isFinished: null,
   });

+ 11 - 8
src/views/student/StudentManage.vue

@@ -47,14 +47,11 @@
         </el-select>
       </el-form-item>
       <el-form-item label="专业类型">
-        <el-select
+        <select-option
           v-model="searchModel.subjectCategory"
-          placeholder="请选择"
-          clearable
-          style="width: 120px"
+          type="subjectCategory"
         >
-          <el-option label="请选择" value="" />
-        </el-select>
+        </select-option>
       </el-form-item>
       <el-form-item label="状态">
         <el-space>
@@ -113,10 +110,16 @@
         </el-input>
       </el-form-item>
       <el-form-item label="学院">
-        <select-college v-model="searchModel.college"> </select-college>
+        <select-option
+          v-model="searchModel.college"
+          type="college"
+        ></select-option>
       </el-form-item>
       <el-form-item label="班级">
-        <select-class v-model="searchModel.className"> </select-class>
+        <select-option
+          v-model="searchModel.className"
+          type="className"
+        ></select-option>
       </el-form-item>
       <el-form-item label="任课老师">
         <el-input

+ 21 - 19
src/views/subject/OptionalRuleEdit.vue

@@ -3,13 +3,13 @@
     <el-space wrap>
       <span>科目:{{ subjectInfo.code }}-{{ subjectInfo.name }}</span>
       <span style="margin-left: 20px"
-        >客观总分:{{ subjectInfo.objectiveTotalScore }}</span
+        >客观总分:{{ subjectInfo.objectiveScore }}</span
       >
       <span style="margin-left: 20px"
-        >主观总分:{{ subjectInfo.subjectiveTotalScore }}</span
+        >主观总分:{{ subjectInfo.subjectiveScore }}</span
       >
       <span style="margin-left: 20px"
-        >试卷总分:{{ subjectInfo.paperTotalScore }}</span
+        >试卷总分:{{ subjectInfo.totalScore }}</span
       >
       <el-button type="primary" @click="onAdd">新增</el-button>
       <el-button type="primary" @click="onEdit"> 编辑 </el-button>
@@ -26,12 +26,12 @@
       border
       stripe
     >
-      <el-table-column prop="bigQuestionName" label="大题名称" width="120" />
-      <el-table-column prop="bigQuestionNo" label="大题号" width="80" />
-      <el-table-column prop="smallQuestionNo" label="小题号" width="80" />
-      <el-table-column prop="fullScore" label="满分" width="80" />
+      <el-table-column prop="mainTitle" label="大题名称" width="120" />
+      <el-table-column prop="mainNumber" label="大题号" width="80" />
+      <el-table-column prop="subNumber" label="小题号" width="80" />
+      <el-table-column prop="totalScore" label="满分" width="80" />
       <el-table-column prop="intervalScore" label="间隔分" width="80" />
-      <el-table-column prop="questionType" label="选做题分组" width="100" />
+      <el-table-column prop="type" label="选做题分组" width="100" />
       <el-table-column prop="answer" label="选做题区" width="100" />
     </el-table>
   </div>
@@ -40,7 +40,7 @@
   <ModifyPaperStructure
     ref="modifyPaperStructureRef"
     :row-data="curRow"
-    :subject-id="subjectId"
+    :subject-id="subjectCode"
     @modified="getList"
   />
 </template>
@@ -62,23 +62,23 @@
   const router = useRouter();
 
   // 从路由参数获取科目ID
-  const subjectId = computed(() => Number(route.params.subjectId) || 0);
+  const subjectCode = computed(() => Number(route.params.subjectCode) || 0);
 
   const subjectInfo = reactive({
     code: '',
     name: '',
-    objectiveTotalScore: 0,
-    subjectiveTotalScore: 0,
-    paperTotalScore: 0,
+    objectiveScore: 0,
+    subjectiveScore: 0,
+    totalScore: 0,
   });
   // 获取科目详情信息
   const getSubjectDetailInfo = async () => {
-    const res = await getSubjectDetail(subjectId.value);
+    const res = await getSubjectDetail(subjectCode.value);
     subjectInfo.code = res.code;
     subjectInfo.name = res.name;
-    subjectInfo.objectiveTotalScore = res.objectiveTotalScore;
-    subjectInfo.subjectiveTotalScore = res.subjectiveTotalScore;
-    subjectInfo.paperTotalScore = res.paperTotalScore;
+    subjectInfo.objectiveScore = res.objectiveScore;
+    subjectInfo.subjectiveScore = res.subjectiveScore;
+    subjectInfo.totalScore = res.totalScore;
   };
 
   // 获取试卷结构列表
@@ -87,7 +87,9 @@
   // 获取试卷结构列表
   const getList = async () => {
     setLoading(true);
-    const res = await getOptionalQuestionList(subjectId.value).catch(() => []);
+    const res = await getOptionalQuestionList(subjectCode.value).catch(
+      () => []
+    );
     setLoading(false);
     dataList.value = res || [];
   };
@@ -105,7 +107,7 @@
   };
 
   onMounted(() => {
-    if (subjectId.value) {
+    if (subjectCode.value) {
       getList();
       getSubjectDetailInfo();
     }

+ 33 - 37
src/views/subject/PaperStructEdit.vue

@@ -3,13 +3,13 @@
     <div class="subject-info">
       <span>科目:{{ subjectInfo.code }}-{{ subjectInfo.name }}</span>
       <span style="margin-left: 20px"
-        >客观总分:{{ subjectInfo.objectiveTotalScore }}</span
+        >客观总分:{{ subjectInfo.objectiveScore }}</span
       >
       <span style="margin-left: 20px"
-        >主观总分:{{ subjectInfo.subjectiveTotalScore }}</span
+        >主观总分:{{ subjectInfo.subjectiveScore }}</span
       >
       <span style="margin-left: 20px"
-        >试卷总分:{{ subjectInfo.paperTotalScore }}</span
+        >试卷总分:{{ subjectInfo.totalScore }}</span
       >
     </div>
 
@@ -28,7 +28,7 @@
       </el-form-item>
       <el-form-item label="大题">
         <el-select
-          v-model="searchModel.bigQuestionNo"
+          v-model="searchModel.mainNumber"
           placeholder="不限"
           clearable
           style="width: 120px"
@@ -68,33 +68,29 @@
       <el-table-column
         type="selection"
         width="55"
-        :selectable="(row) => !row.grouping"
+        :selectable="(row) => !row.groupNumber"
       />
       <el-table-column prop="paperType" label="试卷类型" width="80" />
-      <el-table-column prop="bigQuestionName" label="大题名称" width="120" />
-      <el-table-column
-        prop="bigQuestionNickname"
-        label="大题昵称"
-        width="120"
-      />
-      <el-table-column prop="bigQuestionNo" label="大题号" width="80" />
-      <el-table-column prop="smallQuestionNo" label="小题号" width="80" />
-      <el-table-column prop="fullScore" label="满分" width="80" />
-      <el-table-column prop="giveScoreCount" label="给分次数" width="100" />
+      <el-table-column prop="mainTitle" label="大题名称" width="120" />
+      <el-table-column prop="name" label="大题昵称" width="120" />
+      <el-table-column prop="mainNumber" label="大题号" width="80" />
+      <el-table-column prop="subNumber" label="小题号" width="80" />
+      <el-table-column prop="totalScore" label="满分" width="80" />
+      <el-table-column prop="trackCount" label="给分次数" width="100" />
       <el-table-column label="是否客观题" width="100">
         <template #default="{ row }">
-          <el-tag :type="row.isObjective ? 'success' : 'info'">
-            {{ row.isObjective ? '是' : '否' }}
+          <el-tag :type="row.objective ? 'success' : 'info'">
+            {{ row.objective ? '是' : '否' }}
           </el-tag>
         </template>
       </el-table-column>
       <el-table-column prop="intervalScore" label="间隔分" width="80" />
-      <el-table-column prop="questionType" label="题型" width="100" />
+      <el-table-column prop="type" label="题型" width="100" />
       <el-table-column prop="answer" label="答案" width="100" />
-      <el-table-column prop="scoringStrategy" label="判分策略" width="100" />
-      <el-table-column prop="grouping" label="分组" width="80">
+      <el-table-column prop="objectivePolicy" label="判分策略" width="100" />
+      <el-table-column prop="groupNumber" label="分组" width="80">
         <template #default="{ row }">
-          <span v-if="row.grouping">{{ row.grouping }}</span>
+          <span v-if="row.groupNumber">{{ row.groupNumber }}</span>
           <span v-else style="color: #999">无</span>
         </template>
       </el-table-column>
@@ -104,7 +100,7 @@
             编辑
           </el-button>
           <el-button
-            v-if="!row.grouping"
+            v-if="!row.groupNumber"
             size="small"
             type="danger"
             link
@@ -121,7 +117,7 @@
   <ModifyPaperStructure
     ref="modifyPaperStructureRef"
     :row-data="curRow"
-    :subject-id="subjectId"
+    :subject-id="subjectCode"
     @modified="getList"
   />
 </template>
@@ -149,33 +145,33 @@
   const router = useRouter();
 
   // 从路由参数获取科目ID
-  const subjectId = computed(() => Number(route.params.subjectId) || 0);
+  const subjectCode = computed(() => Number(route.params.subjectCode) || 0);
 
   const subjectInfo = reactive({
     code: '',
     name: '',
-    objectiveTotalScore: 0,
-    subjectiveTotalScore: 0,
-    paperTotalScore: 0,
+    objectiveScore: 0,
+    subjectiveScore: 0,
+    totalScore: 0,
   });
   // 获取科目详情信息
   const getSubjectDetailInfo = async () => {
-    const res = await getSubjectDetail(subjectId.value);
+    const res = await getSubjectDetail(subjectCode.value);
     subjectInfo.code = res.code;
     subjectInfo.name = res.name;
-    subjectInfo.objectiveTotalScore = res.objectiveTotalScore;
-    subjectInfo.subjectiveTotalScore = res.subjectiveTotalScore;
-    subjectInfo.paperTotalScore = res.paperTotalScore;
+    subjectInfo.objectiveScore = res.objectiveScore;
+    subjectInfo.subjectiveScore = res.subjectiveScore;
+    subjectInfo.totalScore = res.totalScore;
   };
 
   interface SearchModel {
     paperType: string;
-    bigQuestionNo: number | string;
+    mainNumber: number | string;
   }
 
   const searchModel = reactive<SearchModel>({
     paperType: '',
-    bigQuestionNo: '',
+    mainNumber: '',
   });
 
   // 获取试卷结构列表
@@ -184,18 +180,18 @@
   const dataList = ref<PaperStructureItem[]>([]);
   // 更新dataList
   const updateDataList = () => {
-    const { paperType, bigQuestionNo } = searchModel;
+    const { paperType, mainNumber } = searchModel;
     dataList.value = structList.value.filter((item) => {
       return (
         (!paperType || item.paperType === paperType) &&
-        (!bigQuestionNo || item.bigQuestionNo === bigQuestionNo)
+        (!mainNumber || item.mainNumber === mainNumber)
       );
     });
   };
   // 获取试卷结构列表
   const getList = async () => {
     setLoading(true);
-    const res = await getPaperStructureList(subjectId.value).catch(() => []);
+    const res = await getPaperStructureList(subjectCode.value).catch(() => []);
     setLoading(false);
     structList.value = res || [];
     updateDataList();
@@ -270,7 +266,7 @@
   };
 
   onMounted(() => {
-    if (subjectId.value) {
+    if (subjectCode.value) {
       getList();
       getSubjectDetailInfo();
     }

+ 62 - 45
src/views/subject/SubjectManage.vue

@@ -23,26 +23,26 @@
           <el-option label="初中" value="初中" />
         </el-select>
       </el-form-item>
-      <el-form-item label="选做科目">
+      <el-form-item label="专业类型">
         <el-select
-          v-model="searchModel.optional"
-          placeholder="不限"
+          v-model="searchModel.category"
+          placeholder="请选择"
           clearable
           style="width: 120px"
         >
-          <el-option label="是" :value="true" />
-          <el-option label="否" :value="false" />
+          <el-option label="理科" value="理科" />
+          <el-option label="文科" value="文科" />
         </el-select>
       </el-form-item>
-      <el-form-item label="专业类型">
+      <el-form-item label="选做科目">
         <el-select
-          v-model="searchModel.majorType"
-          placeholder="请选择"
+          v-model="searchModel.selective"
+          placeholder="不限"
           clearable
           style="width: 120px"
         >
-          <el-option label="理科" value="理科" />
-          <el-option label="文科" value="文科" />
+          <el-option label="是" :value="true" />
+          <el-option label="否" :value="false" />
         </el-select>
       </el-form-item>
       <el-form-item label="状态">
@@ -99,8 +99,8 @@
           </el-button>
           <template #dropdown>
             <el-dropdown-menu>
-              <el-dropdown-item command="fhy">客观题</el-dropdown-item>
-              <el-dropdown-item command="fhy">主观题</el-dropdown-item>
+              <el-dropdown-item command="objective">客观题</el-dropdown-item>
+              <el-dropdown-item command="subjective">主观题</el-dropdown-item>
             </el-dropdown-menu>
           </template>
         </el-dropdown>
@@ -132,10 +132,10 @@
         </template>
       </el-table-column>
       <el-table-column prop="level" label="层次" />
-      <el-table-column prop="majorType" label="专业类型" />
-      <el-table-column prop="optional" label="选做科目">
+      <el-table-column prop="category" label="专业类型" />
+      <el-table-column prop="selective" label="选做科目">
         <template #default="{ row }">
-          {{ row.optional ? '是' : '否' }}
+          {{ row.selective ? '是' : '否' }}
         </template>
       </el-table-column>
       <el-table-column prop="paperUrl" label="试卷">
@@ -165,10 +165,10 @@
         </template>
       </el-table-column>
       <el-table-column prop="paperType" label="试卷类型" />
-      <el-table-column prop="cardFormatType" label="卡格式" />
-      <el-table-column prop="objectiveTotalScore" label="客观总分" />
-      <el-table-column prop="subjectiveTotalScore" label="主观总分" />
-      <el-table-column prop="paperTotalScore" label="试卷总分" />
+      <el-table-column prop="cardType" label="卡格式" />
+      <el-table-column prop="objectiveScore" label="客观总分" />
+      <el-table-column prop="subjectiveScore" label="主观总分" />
+      <el-table-column prop="totalScore" label="试卷总分" />
       <el-table-column prop="status" label="状态">
         <template #default="{ row }">
           <el-tag :type="row.status === '正常' ? 'success' : 'danger'">
@@ -223,6 +223,7 @@
     ref="importDialogRef"
     :title="importConfig?.title"
     :upload-url="importConfig?.url"
+    :upload-data="uploadData"
     :format="importConfig?.format"
     :download-handle="downloadTemplate"
     :download-filename="importConfig?.donloadFilename"
@@ -246,7 +247,7 @@
   } from '@/api/types/subject';
   import useTable from '@/hooks/table';
   import useLoading from '@/hooks/loading';
-  import { useAppStore } from '@/store';
+  import { downloadExport } from '@/utils/download-export';
 
   import SubjectSettingDialog from './components/SubjectSettingDialog.vue';
 
@@ -255,13 +256,12 @@
   });
 
   const router = useRouter();
-  const appStore = useAppStore();
 
   const searchModel = reactive<SubjectListFilter>({
-    subject: null,
+    code: null,
     level: '',
-    majorType: '',
-    optional: undefined,
+    category: '',
+    selective: undefined,
     status: '',
     totalScore: undefined,
   });
@@ -318,10 +318,8 @@
 
   // 获取科目总分统计数据
   const getStatData = async () => {
-    if (!appStore.curExam?.id) return;
     try {
-      // 这里需要传入实际的examId,暂时使用1作为示例
-      const data = await getSubjectTotalScoreStatList(appStore.curExam.id);
+      const data = await getSubjectTotalScoreStatList();
       updateChart(data);
     } catch (error) {
       console.error('获取统计数据失败:', error);
@@ -336,7 +334,7 @@
   const onEditPaperStruct = (row: SubjectItem) => {
     router.push({
       name: 'PaperStructEdit',
-      params: { subjectId: row.id },
+      params: { subjectCode: row.id },
     });
   };
 
@@ -345,7 +343,7 @@
   const onAnalysis = async (row: SubjectItem | undefined) => {
     try {
       setLoading(true);
-      await subjectAnalysis(appStore.curExam.id, row?.id);
+      await subjectAnalysis(row?.code);
     } catch (error) {
       console.error('分析失败:', error);
     } finally {
@@ -356,7 +354,7 @@
   const onObjectiveMark = async () => {
     try {
       setLoading(true);
-      await subjectObjectiveMarkScore(appStore.curExam.id);
+      await subjectObjectiveMarkScore();
     } catch (error) {
       console.error('分析失败:', error);
     } finally {
@@ -367,13 +365,15 @@
   const onSetOptional = (row: SubjectItem) => {
     router.push({
       name: 'OptionalRuleEdit',
-      params: { subjectId: row.id },
+      params: { subjectCode: row.id },
     });
   };
 
   // 导出
   const onExportCommand = (command: string) => {
-    console.log('导出命令:', command);
+    downloadExport('exportSubjectMainObjective', {
+      objective: command === 'objective',
+    });
   };
 
   // 导入
@@ -381,28 +381,28 @@
     {
       code: 'objective',
       title: '客观题',
-      url: '/api/admin/subject/import/kgt',
+      url: '/api/admin/subject/import',
       format: ['xls', 'xlsx'],
       donloadFilename: '客观题导入模板.xlsx',
     },
     {
-      code: 'zgtjg',
-      title: 'subjectivewStruct',
-      url: '/api/admin/subject/import/zgtjg',
+      code: 'subjective',
+      title: '主观题结构',
+      url: '/api/admin/subject/import',
       format: ['xls', 'xlsx'],
       donloadFilename: '主观题结构导入模板.xlsx',
     },
     {
-      code: 'subjectivewGroup',
+      code: 'subjectiveGroup',
       title: '主观题分组',
-      url: '/api/admin/subject/import/zgtfhy',
+      url: '/api/admin/subject/importGroup',
       format: ['xls', 'xlsx'],
       donloadFilename: '主观题分组导入模板.xlsx',
     },
     {
       code: 'package',
       title: '数据包',
-      url: '/api/admin/subject/import/sjb',
+      url: '/api/admin/subject/importData',
       format: ['zip'],
       donloadFilename: undefined,
     },
@@ -411,22 +411,39 @@
   const importData = reactive({
     importType: 'objective',
   });
+  const uploadData = computed(() => {
+    if (importData.importType === 'objective') {
+      return { objective: true };
+    }
+
+    if (importData.importType === 'subjective') {
+      return { objective: false };
+    }
+
+    return undefined;
+  });
   const importConfig = computed(() => {
     return impoartConfigList.value.find(
       (item) => item.code === importData.importType
     );
   });
   const onImportCommand = (command: string) => {
-    console.log('导入命令:', command);
     importData.importType = command;
     importDialogRef.value?.open();
   };
   async function downloadTemplate() {
-    // const res = await downloadByApi(() => agentTemplate()).catch((e) => {
-    //   Message.error(e || '下载失败,请重新尝试!');
-    // });
-    // if (!res) return;
-    // Message.success('下载成功!');
+    if (importData.importType === 'objective') {
+      downloadExport('downloadSubjectObjectiveImportTemplate', {
+        objective: true,
+      });
+    } else if (
+      importData.importType === 'subjective' ||
+      importData.importType === 'subjectiveGroup'
+    ) {
+      downloadExport('downloadSubjectiveImportTemplate', {
+        objective: false,
+      });
+    }
   }
 
   onMounted(() => {

+ 1 - 1
src/views/subject/components/ModifyOptionalQuestionRule.vue

@@ -26,7 +26,7 @@
         <OptionalQuestionSelect
           ref="questionSelectRef"
           v-model="selectedQuestions"
-          :subject-id="props.rowData?.subjectId"
+          :subject-id="props.rowData?.subjectCode"
         />
       </div>
     </div>

+ 65 - 89
src/views/subject/components/ModifyPaperStructure.vue

@@ -27,65 +27,51 @@
         </el-select>
       </el-form-item>
 
-      <el-form-item label="大题名称" prop="bigQuestionName">
-        <el-input
-          v-model="formModel.bigQuestionName"
-          placeholder="请输入大题名称"
-          maxlength="50"
-        />
+      <el-form-item label="大题名称" prop="mainTitle">
+        <el-input v-model="formModel.mainTitle" placeholder="请输入大题名称" />
       </el-form-item>
-
-      <el-form-item label="大题昵称" prop="bigQuestionNickname">
-        <el-input
-          v-model="formModel.bigQuestionNickname"
-          placeholder="请输入大题昵称"
-          maxlength="50"
-        />
+      <el-form-item label="大题昵称" prop="name">
+        <el-input v-model="formModel.name" placeholder="请输入大题昵称" />
       </el-form-item>
-
-      <el-form-item label="大题号" prop="bigQuestionNo">
+      <el-form-item label="大题号" prop="mainNumber">
         <el-input-number
-          v-model="formModel.bigQuestionNo"
+          v-model="formModel.mainNumber"
           :min="1"
           :max="99"
-          placeholder="请输入大题号"
-          style="width: 100%"
+          controls-position="right"
         />
       </el-form-item>
-
-      <el-form-item label="小题号" prop="smallQuestionNo">
+      <el-form-item label="小题号" prop="subNumber">
         <el-input-number
-          v-model="formModel.smallQuestionNo"
+          v-model="formModel.subNumber"
           :min="1"
-          :max="999"
-          placeholder="请输入小题号"
-          style="width: 100%"
+          :max="99"
+          controls-position="right"
         />
       </el-form-item>
-
-      <el-form-item label="满分" prop="fullScore">
+      <el-form-item label="满分" prop="totalScore">
         <el-input-number
-          v-model="formModel.fullScore"
+          v-model="formModel.totalScore"
           :min="0"
           :max="999"
           :precision="1"
-          placeholder="请输入满分"
-          style="width: 100%"
+          controls-position="right"
         />
       </el-form-item>
-
-      <el-form-item label="给分次数" prop="giveScoreCount">
+      <el-form-item label="给分次数" prop="trackCount">
         <el-input-number
-          v-model="formModel.giveScoreCount"
+          v-model="formModel.trackCount"
           :min="1"
-          :max="10"
-          placeholder="请输入给分次数"
-          style="width: 100%"
+          :max="9"
+          controls-position="right"
         />
       </el-form-item>
 
-      <el-form-item label="是否客观题">
-        <el-checkbox v-model="formModel.isObjective">客观题</el-checkbox>
+      <el-form-item label="是否客观题" prop="objective">
+        <el-radio-group v-model="formModel.objective">
+          <el-radio :label="true">是</el-radio>
+          <el-radio :label="false">否</el-radio>
+        </el-radio-group>
       </el-form-item>
 
       <el-form-item label="间隔分" prop="intervalScore">
@@ -99,18 +85,13 @@
         />
       </el-form-item>
 
-      <el-form-item label="题型" prop="questionType">
-        <el-select
-          v-model="formModel.questionType"
-          placeholder="请选择题型"
-          style="width: 100%"
-        >
-          <el-option label="单选" value="单选" />
-          <el-option label="多选" value="多选" />
-          <el-option label="填空" value="填空" />
-          <el-option label="简答" value="简答" />
-          <el-option label="计算" value="计算" />
-          <el-option label="作文" value="作文" />
+      <el-form-item label="题型" prop="type">
+        <el-select v-model="formModel.type" placeholder="请选择题型">
+          <el-option label="单选题" value="single" />
+          <el-option label="多选题" value="multiple" />
+          <el-option label="填空题" value="blank" />
+          <el-option label="简答题" value="short" />
+          <el-option label="论述题" value="essay" />
         </el-select>
       </el-form-item>
 
@@ -122,23 +103,26 @@
         />
       </el-form-item>
 
-      <el-form-item label="判分策略" prop="scoringStrategy">
+      <el-form-item
+        v-if="formModel.objective"
+        label="判分策略"
+        prop="objectivePolicy"
+      >
         <el-select
-          v-model="formModel.scoringStrategy"
+          v-model="formModel.objectivePolicy"
           placeholder="请选择判分策略"
-          style="width: 100%"
         >
-          <el-option label="无" value="无" />
-          <el-option label="全对全错" value="全对全错" />
-          <el-option label="按步给分" value="按步给分" />
+          <el-option label="全对得分" value="all_correct" />
+          <el-option label="部分得分" value="partial" />
         </el-select>
       </el-form-item>
 
-      <el-form-item label="分组" prop="grouping">
-        <el-input
-          v-model="formModel.grouping"
-          placeholder="请输入分组信息(可选)"
-          maxlength="10"
+      <el-form-item label="分组" prop="groupNumber">
+        <el-input-number
+          v-model="formModel.groupNumber"
+          :min="0"
+          :max="99"
+          controls-position="right"
         />
       </el-form-item>
     </el-form>
@@ -178,12 +162,12 @@
 
   interface Props {
     rowData: PaperStructureItem;
-    subjectId: number;
+    subjectCode: string;
   }
 
   const props = withDefaults(defineProps<Props>(), {
     rowData: () => ({} as PaperStructureItem),
-    subjectId: 0,
+    subjectCode: '',
   });
 
   const emit = defineEmits(['modified']);
@@ -194,18 +178,18 @@
   const formRef = ref<FormInstance>();
   const initialFormState: PaperStructureUpdateParams = {
     paperType: '',
-    bigQuestionName: '',
-    bigQuestionNickname: '',
-    bigQuestionNo: 1,
-    smallQuestionNo: 1,
-    fullScore: 0,
-    giveScoreCount: 1,
-    isObjective: false,
+    mainTitle: '',
+    name: '',
+    mainNumber: 1,
+    subNumber: 1,
+    totalScore: 0,
+    trackCount: 1,
+    objective: false,
     intervalScore: 0.5,
-    questionType: '',
+    type: '',
     answer: '',
-    scoringStrategy: '无',
-    grouping: '',
+    objectivePolicy: '',
+    groupNumber: 0,
   };
   const formModel = reactive<PaperStructureUpdateParams>({
     ...initialFormState,
@@ -215,34 +199,26 @@
     paperType: [
       { required: true, message: '请选择试卷类型', trigger: 'change' },
     ],
-    bigQuestionName: [
+    mainTitle: [
       { required: true, message: '请输入大题名称', trigger: 'blur' },
       { max: 50, message: '大题名称不能超过50个字符', trigger: 'blur' },
     ],
-    bigQuestionNickname: [
-      { max: 50, message: '大题昵称不能超过50个字符', trigger: 'blur' },
-    ],
-    bigQuestionNo: [
-      { required: true, message: '请输入大题号', trigger: 'blur' },
-    ],
-    smallQuestionNo: [
-      { required: true, message: '请输入小题号', trigger: 'blur' },
-    ],
-    fullScore: [{ required: true, message: '请输入满分', trigger: 'blur' }],
-    giveScoreCount: [
+    name: [{ max: 50, message: '大题昵称不能超过50个字符', trigger: 'blur' }],
+    mainNumber: [{ required: true, message: '请输入大题号', trigger: 'blur' }],
+    subNumber: [{ required: true, message: '请输入小题号', trigger: 'blur' }],
+    totalScore: [{ required: true, message: '请输入满分', trigger: 'blur' }],
+    trackCount: [
       { required: true, message: '请输入给分次数', trigger: 'blur' },
     ],
     intervalScore: [
       { required: true, message: '请输入间隔分', trigger: 'blur' },
     ],
-    questionType: [
-      { required: true, message: '请选择题型', trigger: 'change' },
-    ],
+    type: [{ required: true, message: '请选择题型', trigger: 'change' }],
     answer: [{ max: 200, message: '答案不能超过200个字符', trigger: 'blur' }],
-    scoringStrategy: [
+    objectivePolicy: [
       { required: true, message: '请选择判分策略', trigger: 'change' },
     ],
-    grouping: [
+    groupNumber: [
       { max: 10, message: '分组信息不能超过10个字符', trigger: 'blur' },
     ],
   };

+ 8 - 8
src/views/subject/components/OptionalQuestionSelect.vue

@@ -9,10 +9,10 @@
     @selection-change="handleSelectionChange"
   >
     <el-table-column type="selection" width="55" />
-    <el-table-column prop="bigQuestionName" label="大题名称" width="120" />
-    <el-table-column prop="bigQuestionNo" label="大题号" width="80" />
-    <el-table-column prop="smallQuestionNo" label="小题号" width="80" />
-    <el-table-column prop="fullScore" label="满分" width="80" />
+    <el-table-column prop="mainTitle" label="大题名称" width="120" />
+    <el-table-column prop="mainNumber" label="大题号" width="80" />
+    <el-table-column prop="subNumber" label="小题号" width="80" />
+    <el-table-column prop="totalScore" label="满分" width="80" />
     <el-table-column prop="intervalScore" label="间隔分" width="80" />
     <el-table-column
       prop="optionalQuestionGroup"
@@ -36,7 +36,7 @@
 
   interface Props {
     modelValue: number[];
-    subjectId: number;
+    subjectCode: string;
   }
 
   const props = defineProps<Props>();
@@ -61,11 +61,11 @@
 
   // 获取试题列表
   const getList = async () => {
-    if (!props.subjectId) return;
+    if (!props.subjectCode) return;
 
     setLoading(true);
     try {
-      const res = await getOptionalQuestionList(props.subjectId);
+      const res = await getOptionalQuestionList(props.subjectCode);
       dataList.value = res || [];
 
       // 设置已选中的项
@@ -109,7 +109,7 @@
   });
 
   onMounted(() => {
-    if (props.subjectId) {
+    if (props.subjectCode) {
       getList();
     }
   });

+ 5 - 6
src/views/subject/components/OptionalRuleDialog.vue

@@ -24,8 +24,7 @@
         </template>
       </el-table-column>
 
-      <el-table-column label="大题名称" prop="bigQuestionName">
-      </el-table-column>
+      <el-table-column label="大题名称" prop="mainTitle"> </el-table-column>
 
       <el-table-column label="分值" width="100">
         <template #default="{ row }">
@@ -79,11 +78,11 @@
   defineExpose({ open, close });
 
   interface Props {
-    subjectId: number;
+    subjectCode: string;
   }
 
   const props = withDefaults(defineProps<Props>(), {
-    subjectId: 0,
+    subjectCode: '',
   });
 
   const emit = defineEmits(['modified']);
@@ -100,11 +99,11 @@
 
   // 加载选做题规则列表
   const getList = async () => {
-    if (!props.subjectId) return;
+    if (!props.subjectCode) return;
 
     setLoading(true);
     try {
-      const data = await getOptionalQuestionRuleList(props.subjectId);
+      const data = await getOptionalQuestionRuleList(props.subjectCode);
       ruleList.value = data || [];
     } catch (error) {
       ElMessage.error('获取选做题规则列表失败');

+ 8 - 8
src/views/subject/components/SubjectSettingDialog.vue

@@ -56,11 +56,11 @@
       </el-row>
 
       <el-form-item label="评卷显示题目昵称">
-        <el-checkbox v-model="formModel.showQuestionNickname" />
+        <el-checkbox v-model="formModel.displayQuestionName" />
       </el-form-item>
 
       <el-form-item label="评卷提交自动定位">
-        <el-radio-group v-model="formModel.autoLocate">
+        <el-radio-group v-model="formModel.autoScroll">
           <el-radio :value="3">设置以考试为准</el-radio>
           <el-radio :value="6">是</el-radio>
           <el-radio :value="9">否</el-radio>
@@ -71,7 +71,7 @@
       </el-form-item>
 
       <el-form-item label="自动对切题卡">
-        <el-radio-group v-model="formModel.autoCut">
+        <el-radio-group v-model="formModel.enableSplit">
           <el-radio :value="3">设置以考试为准</el-radio>
           <el-radio :value="6">是</el-radio>
           <el-radio :value="9">否</el-radio>
@@ -106,7 +106,7 @@
   import { reactive, ref, computed } from 'vue';
   import { ElMessage } from 'element-plus';
   import type { FormInstance, FormRules } from 'element-plus';
-  import { saveSubjectSetting, getSubjectSetting } from '@/api/subject';
+  import { saveSubjectSetting, getSubjectDetail } from '@/api/subject';
   import type { SubjectItem, SubjectSettingInfo } from '@/api/types/subject';
 
   import useModal from '@/hooks/modal';
@@ -140,12 +140,12 @@
     id: 0,
     name: '',
     code: '',
-    showQuestionNickname: false,
+    displayQuestionName: false,
     passScore: 60,
     excellentScore: 85,
     sheetConfig: null,
-    autoLocate: false,
-    autoCut: false,
+    autoScroll: false,
+    enableSplit: false,
   };
 
   const formModel = reactive<SubjectSettingInfo>({
@@ -214,7 +214,7 @@
     if (props.rowData) {
       try {
         // 获取科目设置信息
-        const settingData = await getSubjectSetting(props.rowData.id);
+        const settingData = await getSubjectDetail(props.rowData.code);
         objModifyAssign(formModel, settingData);
       } catch (error) {
         // 如果获取失败,使用默认值