wuheng преди 1 година
родител
ревизия
2143a9590e

+ 1 - 1
.env-config.ts

@@ -7,7 +7,7 @@ const serviceEnv: ServiceEnv = {
     url: 'http://eas-api.edu.koobietech.com'
   },
   test: {
-    url: 'http://localhost:8080'
+    url: 'http://localhost:9081'
   },
   prod: {
     url: 'http://localhost:8080'

+ 11 - 0
src/router/modules/lesson.ts

@@ -47,6 +47,17 @@ const lesson: AuthRoute.Route = {
         icon: 'mdi:sign'
       }
     },
+    {
+      name: 'lesson_score',
+      path: '/lesson/score',
+      component: 'self',
+      meta: {
+        title: '课程考核',
+        i18nTitle: 'message.routes.lesson.score',
+        requiresAuth: true,
+        icon: 'healthicons:i-exam-qualification-outline'
+      }
+    },
     {
       name: 'lesson_student',
       path: '/lesson/student',

+ 2 - 0
src/typings/page-route.d.ts

@@ -64,6 +64,7 @@ declare namespace PageRoute {
     | 'lesson_group'
     | 'lesson_schedule'
     | 'lesson_student'
+    | 'lesson_score'
     | 'management'
     | 'management_attendance'
     | 'management_auth'
@@ -131,6 +132,7 @@ declare namespace PageRoute {
     | 'lesson_group'
     | 'lesson_schedule'
     | 'lesson_student'
+    | 'lesson_score'
     | 'archives_scores'
     | 'archives_students'
     | 'archives_other'

+ 1 - 0
src/views/index.ts

@@ -34,6 +34,7 @@ export const views: Record<
   function_tab: () => import('./function/tab/index.vue'),
   lesson_calendar: () => import('./lesson/calendar/index.vue'),
   lesson_checkin: () => import('./lesson/checkin/index.vue'),
+  lesson_score: () => import('./lesson/score/index.vue'),
   lesson_classroom: () => import('./lesson/classroom/index.vue'),
   lesson_group: () => import('./lesson/group/index.vue'),
   lesson_schedule: () => import('./lesson/schedule/index.vue'),

+ 36 - 0
src/views/lesson/score/api.ts

@@ -0,0 +1,36 @@
+import { request } from '@/service/request';
+
+export interface QueryStudentScoresParams {
+  groupId?: number;
+  studentId?: number;
+  subjectId?: number;
+  categoryId?: number;
+  studentName?: string;
+  score?: Record<string, unknown>;
+}
+
+export interface ScoresPojo {
+  id: number;
+  groupId: number;
+  categoryId: number;
+  subjectId: number;
+  testDate: Date;
+  type: string;
+  studentId: number;
+  groupName: string;
+  studentName: string;
+  score: number;
+  passRate: number;
+  excelRate: number;
+  comment: string;
+  categoryName: string;
+  subjectsName: string;
+}
+
+export function queryStudentScores(
+  pageNum: number,
+  pageSize: number,
+  params: QueryStudentScoresParams
+): Promise<Service.RequestResult<ScoresPojo[]>> {
+  return request.post(`/scores/queryScores?pageNum=${pageNum}&pageSize=${pageSize}`, params);
+}

+ 201 - 0
src/views/lesson/score/curd.ts

@@ -0,0 +1,201 @@
+import type { CreateCrudOptionsRet } from '@fast-crud/fast-crud';
+import dayjs from 'dayjs';
+import { queryStudentScores } from './api';
+export default function createCrudOptions(): CreateCrudOptionsRet {
+  return {
+    crudOptions: {
+      request: {
+        pageRequest: async ({ page, query }) => {
+          const { total, data } = await queryStudentScores(page.offset + 1, page.limit, query);
+          return { records: data, total, currentPage: page.offset, pageSize: page.limit };
+        },
+        addRequest: () => {
+          return Promise.resolve(true);
+        },
+        editRequest: () => {
+          return Promise.resolve(true);
+        },
+        delRequest: () => {
+          return Promise.resolve(true);
+        }
+      },
+      rowHandle: {
+        show: true
+      },
+      toolbar: {
+        show: false
+      },
+      actionbar: {
+        show: false
+      },
+      search: {
+        show: false,
+        buttons: {
+          search: {
+            show: false
+          },
+          reset: {
+            show: false
+          }
+        }
+      },
+      columns: {
+        studentName: {
+          title: '学员姓名',
+          type: 'text',
+          search: {
+            show: true
+          }
+        },
+        categoryName: {
+          title: '类别名称',
+          type: 'text',
+          search: {
+            show: true
+          },
+          form: {
+            show: false
+          }
+        },
+        subjectsName: {
+          title: '科目名称',
+          type: 'text',
+          search: {
+            show: true
+          },
+          form: {
+            show: false
+          }
+        },
+        studentNumber: {
+          title: '学员编号',
+          search: {
+            show: true
+          },
+          type: 'text',
+          column: {
+            align: 'center',
+            width: 240
+          },
+          form: {
+            show: false
+          }
+        },
+        testDate: {
+          title: '考试日期',
+          type: 'datetime',
+          search: { show: false },
+          column: {
+            align: 'center'
+          },
+          form: {
+            show: true
+          },
+          valueBuilder(context) {
+            const { value, row, key } = context;
+            if (value) {
+              row[key] = dayjs(value).valueOf();
+            }
+          },
+          valueResolve(context) {
+            const { value, form, key } = context;
+            if (value) {
+              form[key] = dayjs(value).format('YYYY-MM-DD HH:mm:ss');
+            }
+          }
+        },
+        score: {
+          title: '成绩',
+          type: 'number',
+          search: {
+            show: true
+          },
+          form: {
+            show: true
+          }
+        },
+        createTime: {
+          key: 'createTime',
+          title: '创建时间',
+          type: 'datetime',
+          column: {
+            width: 250,
+            align: 'center'
+          },
+          form: {
+            show: false
+          },
+          search: { show: false },
+          valueBuilder(context) {
+            const { value, row, key } = context;
+            if (value) {
+              row[key] = dayjs(value).valueOf();
+            }
+          },
+          valueResolve(context) {
+            const { value, form, key } = context;
+            if (value) {
+              form[key] = dayjs(value).format('YYYY-MM-DD HH:mm:ss');
+            }
+          }
+        },
+        modifyTime: {
+          title: '修改时间',
+          key: 'modifyTime',
+          type: 'datetime',
+          align: 'center',
+          column: {
+            width: 250,
+            align: 'center'
+          },
+          form: {
+            show: false
+          },
+          valueBuilder(context) {
+            const { value, row, key } = context;
+            if (value) {
+              row[key] = dayjs(value).valueOf();
+            }
+          },
+          valueResolve(context) {
+            const { value, form, key } = context;
+            if (value) {
+              form[key] = dayjs(value).format('YYYY-MM-DD HH:mm:ss');
+            }
+          }
+        },
+        passRate: {
+          title: '及格率',
+          type: 'text',
+          search: { show: false },
+          column: {
+            align: 'center'
+          },
+          form: {
+            show: false
+          }
+        },
+        excelRate: {
+          title: '通过率',
+          type: 'text',
+          search: { show: false },
+          column: {
+            align: 'center'
+          },
+          form: {
+            show: false
+          }
+        },
+        comment: {
+          title: '评语',
+          type: 'textarea',
+          search: { show: false },
+          column: {
+            align: 'center',
+            width: 200
+          }
+        }
+      }
+    }
+  };
+}

+ 16 - 0
src/views/lesson/score/index.vue

@@ -0,0 +1,16 @@
+<template>
+  <div class="h-full bg-white">
+    <fs-crud ref="crudRef" v-bind="crudBinding" />
+  </div>
+</template>
+<script setup lang="ts">
+import { onMounted } from 'vue';
+import { useFs } from '@fast-crud/fast-crud';
+import createCrudOptions from './curd';
+
+const { crudRef, crudBinding, crudExpose } = useFs({ createCrudOptions });
+onMounted(() => {
+  crudExpose.doRefresh();
+});
+</script>
+<style scoped lang="scss"></style>

+ 73 - 0
src/views/management/route/api.ts

@@ -113,3 +113,76 @@ export function studentRequest(
 ): Promise<Service.RequestResult<StudentParams[]>> {
   return request.post(`/student/query?pageNum=${pageNum}&pageSize=${pageSize}`, params);
 }
+
+// 参数接口
+export interface QueryStudentScoresParams {
+  groupId?: number;
+  studentId?: number;
+  subjectId?: number;
+  categoryId?: number;
+  studentName?: string;
+  score?: Record<string, unknown>;
+}
+
+export interface ScoresPojo {
+  id: number;
+  groupId: number;
+  categoryId: number;
+  subjectId: number;
+  testDate: Date;
+  type: string;
+  studentId: number;
+  groupName: string;
+  studentName: string;
+  score: number;
+  passRate: number;
+  excelRate: number;
+  comment: string;
+  categoryName: string;
+  subjectsName: string;
+}
+
+export function queryStudentScores(
+  pageNum: number,
+  pageSize: number,
+  params: QueryStudentScoresParams
+): Promise<Service.RequestResult<ScoresPojo[]>> {
+  return request.post(`/scores/queryScores?pageNum=${pageNum}&pageSize=${pageSize}`, params);
+}
+
+// 参数接口
+export interface AddScoreParams {
+  id?: number;
+  studentNumber?: string;
+  categoryId?: number;
+  subjectId?: number;
+  testDate?: Record<string, unknown>;
+  score?: number;
+  passRate?: number;
+  excelRate?: number;
+  createTime?: Record<string, unknown>;
+  modifyTime?: Record<string, unknown>;
+  createUid?: number;
+  comment?: string;
+}
+
+/**
+ * 添加学生成绩信息
+ * @param {object} params EasArcTlsScores
+ * @param {number} params.id ID
+ * @param {string} params.studentNumber 学员档案
+ * @param {number} params.categoryId 类目ID
+ * @param {number} params.subjectId 科目ID
+ * @param {object} params.testDate 考试时间
+ * @param {number} params.score 分数
+ * @param {number} params.passRate 通过率
+ * @param {number} params.excelRate 及格率
+ * @param {object} params.createTime 创建时间
+ * @param {object} params.modifyTime 修改时间
+ * @param {number} params.createUid 创建用户ID
+ * @param {string} params.comment 考试备注信息  如 第二次考试  第三次考试
+ * @returns
+ */
+export function addStudentScore(params: AddScoreParams): Promise<Service.RequestResult<Res>> {
+  return request.post(`/scores/add`, params);
+}

+ 14 - 1
src/views/management/route/component/crud.ts

@@ -85,6 +85,19 @@ export default function createCrudOptions(crudOptionsProps: CreateCrudOptionsPro
             }
           }
         },
+        score: {
+          title: '成绩',
+          type: 'number',
+          search: {
+            show: false
+          },
+          form: {
+            show: false
+          },
+          column: {
+            align: 'center'
+          }
+        },
         createTime: {
           key: 'createTime',
           title: '创建时间',
@@ -151,7 +164,7 @@ export default function createCrudOptions(crudOptionsProps: CreateCrudOptionsPro
           search: { show: false },
           column: {
             align: 'center',
-            width: 200
+            width: 300
           }
         }
       }

+ 225 - 0
src/views/management/route/component/curd.ts

@@ -0,0 +1,225 @@
+import type { CreateCrudOptionsRet, CreateCrudOptionsProps } from '@fast-crud/fast-crud';
+import dayjs from 'dayjs';
+import { queryStudentScores, addStudentScore } from '../api';
+export default function createCrudOptions(crudOptionsProps: CreateCrudOptionsProps): CreateCrudOptionsRet {
+  return {
+    crudOptions: {
+      request: {
+        pageRequest: async ({ page, query }) => {
+          query.subjectId = crudOptionsProps.context?.subjectId;
+          const { total, data } = await queryStudentScores(page.offset + 1, page.limit, query);
+          return { records: data, total, currentPage: page.offset, pageSize: page.limit };
+        },
+        addRequest: ({ form, row }) => {
+          return addStudentScore({
+            studentNumber: form.studentNumber,
+            categoryId: row.categoryId,
+            subjectId: row.subjectId,
+            testDate: form.testDate,
+            score: form.score,
+            comment: form.comment,
+            id: row.id
+          });
+        },
+        editRequest: ({ form, row }) => {
+          return addStudentScore({
+            studentNumber: form.studentNumber,
+            categoryId: row.categoryId,
+            subjectId: row.subjectId,
+            testDate: form.testDate,
+            score: form.score,
+            comment: form.comment,
+            id: row.id
+          });
+        },
+        delRequest: () => {
+          return Promise.resolve(true);
+        }
+      },
+      rowHandle: {
+        show: true
+      },
+      toolbar: {
+        show: false
+      },
+      actionbar: {
+        show: false
+      },
+      search: {
+        show: true,
+        buttons: {
+          search: {
+            show: true
+          },
+          reset: {
+            show: true
+          },
+          custom: {
+            show: true,
+            text: '关闭',
+            click: () => {
+              crudOptionsProps.context?.viewActiveCloseFunc();
+            }
+          }
+        }
+      },
+      columns: {
+        studentName: {
+          title: '学员姓名',
+          type: 'text',
+          search: {
+            show: true
+          }
+        },
+        categoryName: {
+          title: '类别名称',
+          type: 'text',
+          search: {
+            show: true
+          },
+          form: {
+            show: false
+          }
+        },
+        subjectsName: {
+          title: '科目名称',
+          type: 'text',
+          search: {
+            show: true
+          },
+          form: {
+            show: false
+          }
+        },
+        studentNumber: {
+          title: '学员编号',
+          search: {
+            show: true
+          },
+          type: 'text',
+          column: {
+            align: 'center',
+            width: 240
+          },
+          form: {
+            show: false
+          }
+        },
+        testDate: {
+          title: '考试日期',
+          type: 'datetime',
+          search: { show: false },
+          column: {
+            align: 'center'
+          },
+          form: {
+            show: true
+          },
+          valueBuilder(context) {
+            const { value, row, key } = context;
+            if (value) {
+              row[key] = dayjs(value).valueOf();
+            }
+          },
+          valueResolve(context) {
+            const { value, form, key } = context;
+            if (value) {
+              form[key] = dayjs(value).format('YYYY-MM-DD HH:mm:ss');
+            }
+          }
+        },
+        score: {
+          title: '成绩',
+          type: 'number',
+          search: {
+            show: true
+          },
+          form: {
+            show: true
+          }
+        },
+        createTime: {
+          key: 'createTime',
+          title: '创建时间',
+          type: 'datetime',
+          column: {
+            width: 250,
+            align: 'center'
+          },
+          form: {
+            show: false
+          },
+          search: { show: false },
+          valueBuilder(context) {
+            const { value, row, key } = context;
+            if (value) {
+              row[key] = dayjs(value).valueOf();
+            }
+          },
+          valueResolve(context) {
+            const { value, form, key } = context;
+            if (value) {
+              form[key] = dayjs(value).format('YYYY-MM-DD HH:mm:ss');
+            }
+          }
+        },
+        modifyTime: {
+          title: '修改时间',
+          key: 'modifyTime',
+          type: 'datetime',
+          align: 'center',
+          column: {
+            width: 250,
+            align: 'center'
+          },
+          form: {
+            show: false
+          },
+          valueBuilder(context) {
+            const { value, row, key } = context;
+            if (value) {
+              row[key] = dayjs(value).valueOf();
+            }
+          },
+          valueResolve(context) {
+            const { value, form, key } = context;
+            if (value) {
+              form[key] = dayjs(value).format('YYYY-MM-DD HH:mm:ss');
+            }
+          }
+        },
+        passRate: {
+          title: '及格率',
+          type: 'text',
+          search: { show: false },
+          column: {
+            align: 'center'
+          },
+          form: {
+            show: false
+          }
+        },
+        excelRate: {
+          title: '通过率',
+          type: 'text',
+          search: { show: false },
+          column: {
+            align: 'center'
+          },
+          form: {
+            show: false
+          }
+        },
+        comment: {
+          title: '评语',
+          type: 'textarea',
+          search: { show: false },
+          column: {
+            align: 'center',
+            width: 200
+          }
+        }
+      }
+    }
+  };
+}

+ 102 - 0
src/views/management/route/component/importScores.vue

@@ -0,0 +1,102 @@
+<template>
+  <div class="h-full bg-white">
+    <n-tabs type="line" class="h-full" animated justify-content="space-evenly">
+      <n-tab-pane class="h-full" name="填写表格" tab="填写表格">
+        <fs-crud ref="crudRef" v-bind="crudBinding" />
+      </n-tab-pane>
+      <n-tab-pane name="导入Excel" tab="导入Excel">
+        <div style="height: 12vh"></div>
+        <n-card style="width: 50%; margin: 0 auto">
+          <n-button style="position: relative; top: -4rem; float: right" @click="closeTabs">关闭</n-button>
+          <n-space> 当前上传操作用于处理 批量导入 学员成绩分数功能 </n-space>
+          <n-space> 上传分数表格前, 请先下载模板表格 填写数据 </n-space>
+          <n-space>
+            必须使用标准模板导入数据才能被解析,
+            <n-button @click="downloadTemplate">点击我下载分数模板表格</n-button>
+          </n-space>
+          <div style="height: 8vh"></div>
+          <n-upload
+            class="flex-col-center"
+            multiple
+            directory-dnd
+            :action="uploadFile"
+            :max="1"
+            @before-upload="beforeUpload"
+            @finish="handleFinish"
+          >
+            <n-upload-dragger>
+              <div>
+                <svg-icon icon="material-symbols:upload" class="w-30 h-30" style="margin: 0 auto" />
+              </div>
+              <n-text style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </n-text>
+              <n-p depth="3" style="margin: 8px 0 0 0">
+                请不要上传敏感数据,比如你的银行卡号和密码,信用卡号有效期和安全码 上传成功后, 后台会自动解析
+                学员的成绩分数, 并录入成绩库
+              </n-p>
+            </n-upload-dragger>
+          </n-upload>
+          <div style="height: 8vh"></div>
+        </n-card>
+      </n-tab-pane>
+    </n-tabs>
+  </div>
+</template>
+<script setup lang="ts">
+import { onMounted } from 'vue';
+import type { UploadFileInfo } from 'naive-ui';
+import { useFs } from '@fast-crud/fast-crud';
+import importScoresFile from '@/public/分数导入.xlsx';
+import { getServiceEnvConfig } from '~/.env-config';
+import createCrudOptions from './curd';
+const { url, proxyPattern } = getServiceEnvConfig(import.meta.env);
+const uploadFile = proxyPattern ? `${proxyPattern}/scores/import` : `${url}/scores/import`;
+
+interface Emits {
+  (e: 'closeEmits', value: boolean): void;
+}
+const emits = defineEmits<Emits>();
+
+const props = defineProps({
+  subjectId: {
+    type: Number,
+    default: 0
+  }
+});
+
+const context = {
+  subjectId: props.subjectId,
+  viewActiveCloseFunc: () => {
+    emits('closeEmits', true);
+  }
+};
+
+function closeTabs() {
+  emits('closeEmits', true);
+}
+function handleFinish() {
+  emits('closeEmits', true);
+  window.$message?.success('上传成功');
+}
+
+function beforeUpload(data: { file: UploadFileInfo; fileList: UploadFileInfo[] }) {
+  if (data.file.file?.type !== 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
+    window.$message?.error('只能上传 Excel 格式的表格文件 请重新上传');
+    return false;
+  }
+  return true;
+}
+
+function downloadTemplate() {
+  window.location.href = importScoresFile;
+}
+
+const { crudRef, crudBinding, crudExpose } = useFs({ createCrudOptions, context });
+onMounted(() => {
+  crudExpose.doRefresh();
+});
+</script>
+<style scoped lang="scss">
+:deep(.n-tabs-pane-wrapper) {
+  height: 100%;
+}
+</style>

+ 0 - 0
src/views/management/route/component/index.vue → src/views/management/route/component/viewScores.vue


+ 13 - 60
src/views/management/route/index.vue

@@ -1,39 +1,16 @@
 <template>
   <div class="h-full bg-white">
     <fs-crud ref="crudRef" v-bind="crudBinding" />
-
-    <n-drawer v-model:show="importActive" default-width="60%" placement="right" resizable>
-      <n-drawer-content title="导入科目学员成绩">
-        <n-space> 当前上传操作用于处理 批量导入 学员成绩分数功能 </n-space>
-        <n-space> 上传分数表格前, 请先下载模板表格 填写数据 </n-space>
-        <n-space>
-          必须使用标准模板导入数据才能被解析,
-          <n-button @click="downloadTemplate">点击我下载分数模板表格</n-button>
-        </n-space>
-        <n-upload
-          class="flex-col-center"
-          style="margin-top: 30%"
-          multiple
-          directory-dnd
-          :action="uploadFile"
-          :max="1"
-          @before-upload="beforeUpload"
-          @finish="handleFinish"
-        >
-          <n-upload-dragger>
-            <div>
-              <svg-icon icon="material-symbols:upload" class="w-30 h-30" style="margin: 0 auto" />
-            </div>
-            <n-text style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </n-text>
-            <n-p depth="3" style="margin: 8px 0 0 0">
-              请不要上传敏感数据,比如你的银行卡号和密码,信用卡号有效期和安全码 上传成功后, 后台会自动解析
-              学员的成绩分数, 并录入成绩库
-            </n-p>
-          </n-upload-dragger>
-        </n-upload>
-      </n-drawer-content>
+    <n-drawer v-model:show="importActive" default-width="100%" placement="right" resizable>
+      <ImportScores
+        :subject-id="subjectId"
+        @close-emits="
+          () => {
+            importActive = false;
+          }
+        "
+      />
     </n-drawer>
-
     <n-drawer v-model:show="viewActive" default-width="100%" placement="left" resizable>
       <n-drawer-content title="查看科目学员成绩">
         <ScoresView
@@ -51,21 +28,17 @@
 
 <script setup lang="ts">
 import { onMounted, ref } from 'vue';
-import type { UploadFileInfo } from 'naive-ui';
 import { useFs } from '@fast-crud/fast-crud';
-import importScores from '@/public/分数导入.xlsx';
-import { getServiceEnvConfig } from '~/.env-config';
 import createCrudOptions from './crud';
-import ScoresView from './component/index.vue';
+import ScoresView from './component/viewScores.vue';
+import ImportScores from './component/importScores.vue';
 const importActive = ref<boolean>(false);
 const viewActive = ref<boolean>(false);
 const subjectId = ref<number>(0);
-const { url, proxyPattern } = getServiceEnvConfig(import.meta.env);
 const context: any = {
   importActiveFunc: (id: number) => {
-    if (id > 0) {
-      importActive.value = true;
-    }
+    subjectId.value = id;
+    importActive.value = true;
   },
   viewActiveFunc: (id: number) => {
     subjectId.value = id;
@@ -74,29 +47,9 @@ const context: any = {
 };
 const { crudRef, crudBinding, crudExpose } = useFs({ createCrudOptions, context });
 
-const uploadFile = proxyPattern ? `${proxyPattern}/scores/import` : `${url}/scores/import`;
-function handleFinish() {
-  crudExpose.doRefresh();
-  importActive.value = false;
-  window.$message?.success('上传成功');
-}
-
-function beforeUpload(data: { file: UploadFileInfo; fileList: UploadFileInfo[] }) {
-  if (data.file.file?.type !== 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
-    window.$message?.error('只能上传 Excel 格式的表格文件 请重新上传');
-    return false;
-  }
-  return true;
-}
-
-function downloadTemplate() {
-  window.location.href = importScores;
-}
-
 onMounted(() => {
   crudExpose.doRefresh();
 });
 </script>
 
 <style scoped></style>
-./component/scoreCrud