Browse Source

老师端对接

shmily1213 1 month ago
parent
commit
52482df4f1
31 changed files with 978 additions and 345 deletions
  1. 76 2
      src/api/modules/study.ts
  2. 4 4
      src/components/ie-picker/ie-picker.vue
  3. 2 2
      src/components/ie-popup-toolbar/ie-popup-toolbar.vue
  4. 6 2
      src/components/ie-popup/ie-popup.vue
  5. 35 14
      src/composables/useCalendar.ts
  6. 18 0
      src/pages.json
  7. 7 2
      src/pagesMain/pages/me/components/me-info.vue
  8. 5 8
      src/pagesStudy/components/practice-table.vue
  9. 10 11
      src/pagesStudy/components/teacher-class-view.vue
  10. 2 8
      src/pagesStudy/components/video-table.vue
  11. 83 0
      src/pagesStudy/pages/study-exam-simulated-class/study-exam-simulated-class.vue
  12. 84 0
      src/pagesStudy/pages/study-exam-simulated-detail/study-exam-simulated-detail.vue
  13. 85 0
      src/pagesStudy/pages/study-exam-simulated-student/study-exam-simulated-student.vue
  14. 232 0
      src/pagesStudy/pages/study-history/components/exam-history-paperwork.vue
  15. 74 0
      src/pagesStudy/pages/study-history/components/exam-history-simulated.vue
  16. 4 166
      src/pagesStudy/pages/study-history/components/exam-history-teacher.vue
  17. 21 29
      src/pagesStudy/pages/study-history/components/knowledge-history-teacher.vue
  18. 1 1
      src/pagesStudy/pages/study-history/components/knowledge-history.vue
  19. 1 4
      src/pagesStudy/pages/study-history/components/practice-history-student.vue
  20. 25 29
      src/pagesStudy/pages/study-history/components/practice-history-teacher.vue
  21. 8 8
      src/pagesStudy/pages/study-history/components/practice-history.vue
  22. 12 8
      src/pagesStudy/pages/study-history/components/student-stat-table.vue
  23. 1 1
      src/pagesStudy/pages/study-history/components/video-history-student.vue
  24. 44 14
      src/pagesStudy/pages/study-history/components/video-history-teacher.vue
  25. 6 6
      src/pagesStudy/pages/study-history/components/video-history.vue
  26. 24 3
      src/pagesStudy/pages/study-knowledge-detail/study-knowledge-detail.vue
  27. 2 2
      src/pagesStudy/pages/study-practice-detail/study-practice-detail.vue
  28. 20 2
      src/pagesStudy/pages/study-video-detail/study-video-detail.vue
  29. 2 2
      src/pagesSystem/pages/edit-profile/edit-profile.vue
  30. 3 1
      src/pagesSystem/pages/login/login.vue
  31. 81 16
      src/types/study.ts

+ 76 - 2
src/api/modules/study.ts

@@ -1,6 +1,6 @@
 import { ApiResponse, ApiResponseList } from "@/types";
 import flyio from "../flyio";
-import { DirectedSchool, Examinee, ExamPaper, ExamPaperSubmit, GetExamPaperRequestDTO, Knowledge, KnowledgeListRequestDTO, KnowledgeRecord, OpenExamineeRequestDTO, PaperWork, PracticeHistory, PracticeRecord, SimulatedRecord, SimulationExamSubject, SimulationTestInfo, StudyPlan, Subject, SubjectListRequestDTO, VideoStudyRecord } from "@/types/study";
+import { Batch, ClassKnowledgeRecord, DirectedSchool, Examinee, ExamPaper, ExamPaperSubmit, GetExamPaperRequestDTO, Knowledge, KnowledgeListRequestDTO, KnowledgeRecord, OpenExamineeRequestDTO, PaperWork, PaperWorkRecord, PaperWorkRecordDetail, PaperWorkRecordQuery, PracticeHistory, PracticeRecord, SimulatedRecord, SimulationExamSubject, SimulationTestInfo, StudentExamRecord, StudentPlanStudyRecord, StudentVideoRecord, StudyPlan, Subject, SubjectListRequestDTO, TeachClass, VideoStudy } from "@/types/study";
 import { EnumPaperWorkState } from "@/common/enum";
 
 /**
@@ -176,7 +176,7 @@ export function getPlanStudyRecord(params: { year: number, month: number }) {
  * @returns 
  */
 export function getVideoStudyRecord() {
-  return flyio.get('/front/student/record/video') as Promise<ApiResponse<VideoStudyRecord>>;
+  return flyio.get('/front/student/record/video') as Promise<ApiResponse<VideoStudy>>;
 }
 
 
@@ -239,4 +239,78 @@ export function getPaperWorkDetail(id: number) {
 
 export function getPaperWorkStatistic() {
   return flyio.get('/front/student/paperStats', {}) as Promise<ApiResponse<Record<string, number>[]>>;
+}
+
+// 老师端 API
+/**
+ * 获取教学班级列表
+ */
+export function getTeachClassList(params: any) {
+  return flyio.get('/front/teacher/classes', params) as Promise<ApiResponse<TeachClass[]>>;
+}
+
+export function getClassKnowledgeRecord(params: any) {
+  return flyio.get('/front/teacher/record/knowledge', params) as Promise<ApiResponse<ClassKnowledgeRecord>>;
+}
+
+export function getStudentKnowledgeRecord(params: any) {
+  return flyio.get(`/front/teacher/record/knowledge/${params.recordId}`, params) as Promise<ApiResponseList<KnowledgeRecord>>;
+}
+
+export function getClassPlanStudyRecord(params: any) {
+  return flyio.get('/front/teacher/record/planStudy', params) as Promise<ApiResponseList<StudentPlanStudyRecord>>;
+}
+
+export function getStudentPracticeRecord(params: any) {
+  return flyio.get(`/front/teacher/record/planStudy/${params.recordId}`, params) as Promise<ApiResponse<PracticeRecord>>;
+}
+
+export function getClassVideoStudyRecord(params: any) {
+  return flyio.get('/front/teacher/record/video', params) as Promise<ApiResponseList<StudentVideoRecord>>;
+}
+
+export function getStudentVideoStudyRecord(params: any) {
+  return flyio.get(`/front/teacher/record/video/${params.recordId}`, params) as Promise<ApiResponse<VideoStudy>>;
+}
+
+export function getClassExamRecord(params: any) {
+  return flyio.get('/front/teacher/record/simulated', params) as Promise<ApiResponse<StudentExamRecord[]>>;
+}
+
+export function getStudentExamRecord(params: any) {
+  return flyio.get(`/front/teacher/record/simulated/${params.classId}`, params) as Promise<ApiResponse<StudentExamRecord[]>>;
+}
+
+export function getStudentExamSubjectRecord(params: any) {
+  return flyio.get(`/front/teacher/record/simulated/subject/${params.studentId}`, params) as Promise<ApiResponse<StudentExamRecord[]>>;
+}
+
+export function getStudentExamSubjectDetail(params: any) {
+  return flyio.get('/front/teacher/record/simulated/knowledge', params) as Promise<ApiResponse<StudentExamRecord[]>>;
+}
+
+export function getBatchList(params: any) {
+  return flyio.get('/front/teacher/batchs', params) as Promise<ApiResponse<Batch[]>>;
+}
+
+/**
+ * 获取教师科目列表
+ * @param params 
+ * @returns 
+ */
+export function getTeacherSubjectList(params: any) {
+  return flyio.get('/front/teacher/subjects', params) as Promise<ApiResponse<Subject[]>>;
+}
+
+export function getTeacherTestRecord(params: any) {
+  return flyio.get('/front/teacher/record/test', params) as Promise<ApiResponse<PaperWorkRecord[]>>;
+}
+
+
+export function getTeacherTestRecordDetail(params: any) {
+  return flyio.get('/front/teacher/record/test/detail', params) as Promise<ApiResponse<PaperWorkRecordDetail[]>>;
+}
+
+export function getTeacherTestRecordCondition(params: any) {
+  return flyio.get('/front/teacher/record/test/cond', params) as Promise<ApiResponse<PaperWorkRecordQuery>>;
 }

+ 4 - 4
src/components/ie-picker/ie-picker.vue

@@ -1,13 +1,13 @@
 <template>
   <view class="w-full" @click="handleClick">
-    <view class="flex items-center gap-x-10" :style="customStyle">
-      <view v-if="matchValue || customLabel" class="flex-1 text-fore-title" :style="getValueStyle"
-        :class="{ 'text-[#c0c4cc]': disabled || readonly }">
+    <view class="flex items-center gap-x-6" :style="customStyle">
+      <view v-if="matchValue || customLabel" class="text-fore-title flex-1 min-w-1 ellipsis-1" :style="getValueStyle"
+        :class="{ 'text-[#dce4f6]': disabled || readonly }">
         <slot :label="label">
           {{ label }}
         </slot>
       </view>
-      <view v-else class="flex-1 text-[#c0c4cc]" :style="getPlaceholderStyle">{{ placeholder }}</view>
+      <view v-else class=" text-[#c0c4cc]" :style="getPlaceholderStyle">{{ placeholder }}</view>
       <slot name="right">
         <view v-if="!readonly && showArrow" class="transition-all duration-300">
           <uv-icon :name="icon" size="15" color="#B3B3B3" />

+ 2 - 2
src/components/ie-popup-toolbar/ie-popup-toolbar.vue

@@ -1,7 +1,7 @@
 <template>
-  <view class="flex items-center justify-between pt-20">
+  <view class="flex items-center justify-center pt-20">
     <view v-if="showCancel" class="px-46 py-20 text-28 text-fore-light" @click="handleCancel">{{ cancelText }}</view>
-    <text class="text-30 text-fore-title font-bold">{{ title }}</text>
+    <text class="text-30 text-fore-title font-bold flex-1 text-center">{{ title }}</text>
     <view v-if="showConfirm" class="px-46 py-20 text-28 text-fore-title" @click="handleConfirm">{{ confirmText }}</view>
   </view>
 </template>

+ 6 - 2
src/components/ie-popup/ie-popup.vue

@@ -7,8 +7,10 @@
       <!-- #endif -->
       <uv-popup ref="popupRef" :mode="mode" :round="round" popup-class="theme-ie"
         :close-on-click-overlay="closeOnClickOverlay" @close="handleClose">
-        <ie-popup-toolbar :title="title" :cancelText="cancelText" :confirmText="confirmText" :showCancel="showCancel"
-          :showConfirm="showConfirm" @cancel="handleCancel" @confirm="handleConfirm" />
+        <template v-if="showToolbar">
+          <ie-popup-toolbar :title="title" :cancelText="cancelText" :confirmText="confirmText" :showCancel="showCancel"
+            :showConfirm="showConfirm" @cancel="handleCancel" @confirm="handleConfirm" />
+        </template>
         <view class="popup-content">
           <slot></slot>
         </view>
@@ -28,6 +30,7 @@ type Props = {
   mode: 'bottom' | 'center' | 'top';
   round: number;
   closeOnClickOverlay: boolean;
+  showToolbar: boolean;
   showCancel: boolean;
   showConfirm: boolean;
 }
@@ -38,6 +41,7 @@ const props = withDefaults(defineProps<Props>(), {
   mode: 'bottom',
   round: 16,
   closeOnClickOverlay: true,
+  showToolbar: true,
   showCancel: true,
   showConfirm: true
 });

+ 35 - 14
src/composables/useCalendar.ts

@@ -1,7 +1,9 @@
 import { ref, computed, watch } from 'vue';
-import { getPlanStudyRecord } from '@/api/modules/study';
+import { getPlanStudyRecord, getStudentPracticeRecord } from '@/api/modules/study';
+import { useUserStore } from '@/store/userStore';
 // @ts-ignore
 import CalendarUtil from '@/uni_modules/uni-calendar/components/uni-calendar/util.js';
+import { PracticeRecord } from '@/types/study';
 
 export interface PracticeStatistics {
   list: PracticeData[],
@@ -18,7 +20,8 @@ export interface PracticeData {
 
 export function useCalendar() {
   const calendarUtil = new CalendarUtil();
-  const globalStudentId = ref(0);
+  const { isStudent } = storeToRefs(useUserStore());
+  const globalRecordId = ref(0);
   const selected = ref<PracticeData[]>([]);
   const statistics = ref<PracticeStatistics>({
     list: [],
@@ -73,7 +76,6 @@ export function useCalendar() {
   // 4. 统计数据会从返回的练习数据中自动计算,无需额外处理
   // ========================================================
   const fetchPracticeData = async (year: number, month: number): Promise<PracticeStatistics> => {
-    console.log('请求数据', year, month, globalStudentId.value)
     let practiceData: PracticeStatistics = {
       list: [],
       rate: 0,
@@ -81,20 +83,37 @@ export function useCalendar() {
       total: 0
     };
 
-    const { data } = await getPlanStudyRecord({
-      year,
-      month
-    });
-    if (data && Array.isArray(data.list)) {
-      practiceData.list = data.list.map(item => ({
+    let resData: PracticeRecord = {
+      list: [],
+      rate: 0,
+      studyDays: 0,
+      total: 0
+    };
+    if (isStudent.value) {
+      const { data } = await getPlanStudyRecord({
+        year,
+        month
+      });
+      resData = data;
+    } else {
+      // 老师查看学生刷题记录
+      const { data } = await getStudentPracticeRecord({
+        recordId: globalRecordId.value,
+        year,
+        month
+      });
+      resData = data;
+    }
+    if (resData && Array.isArray(resData.list)) {
+      practiceData.list = resData.list.map(item => ({
         date: item.date,
         info: item.rate ? Number(item.rate) : 0,
         questionNum: item.study ? Number(item.study) : 0
       })).sort((a, b) => a.date.localeCompare(b.date));
     }
-    practiceData.rate = data.rate ? Number(data.rate) : 0;
-    practiceData.studyDays = data.studyDays ? Number(data.studyDays) : 0;
-    practiceData.total = data.total ? Number(data.total) : 0;
+    practiceData.rate = resData.rate ? Number(resData.rate) : 0;
+    practiceData.studyDays = resData.studyDays ? Number(resData.studyDays) : 0;
+    practiceData.total = resData.total ? Number(resData.total) : 0;
     return practiceData;
   };
 
@@ -197,9 +216,11 @@ export function useCalendar() {
   };
 
   // 初始化数据 - 默认按年份模式初始化
-  const init = async (studentId: number) => {
+  const init = async (recordId?: number) => {
     const today = new Date();
-    globalStudentId.value = studentId;
+    if (recordId) {
+      globalRecordId.value = recordId;
+    }
     await updateCalendarData(today.getFullYear(), 0);
   };
 

+ 18 - 0
src/pages.json

@@ -679,6 +679,24 @@
           "style": {
             "navigationBarTitleText": ""
           }
+        },
+        {
+          "path": "pages/study-exam-simulated-class/study-exam-simulated-class",
+          "style": {
+            "navigationBarTitleText": ""
+          }
+        },
+        {
+          "path": "pages/study-exam-simulated-student/study-exam-simulated-student",
+          "style": {
+            "navigationBarTitleText": ""
+          }
+        },
+        {
+          "path": "pages/study-exam-simulated-detail/study-exam-simulated-detail",
+          "style": {
+            "navigationBarTitleText": ""
+          }
         }
       ]
     }

+ 7 - 2
src/pagesMain/pages/me/components/me-info.vue

@@ -18,7 +18,7 @@
       </view>
     </view>
 
-    <view class="mt-50 h-184 rounded-15 relative bg-gradient-to-r from-[#253045] to-[#141D2F] overflow-hidden">
+    <view v-if="isStudent" class="mt-50 h-184 rounded-15 relative bg-gradient-to-r from-[#253045] to-[#141D2F] overflow-hidden">
       <ie-image src="/static/personal/bg-vip-card.png"
         custom-class="w-276 h-full absolute left-1/2 top-0 -translate-x-1/2 z-0" mode="heightFix" />
       <view class="h-full box-border px-45 flex items-center justify-between relative z-1">
@@ -26,7 +26,7 @@
           <view class="flex items-center justify-between">
             <ie-image src="/static/personal/icon-vip.png" custom-class="w-210 h-46" />
             <view v-if="!isVip" class="flex items-center justify-center px-20 py-10 rounded-22"
-              style="background: radial-gradient( 0% 0% at 0% 0%, #FEE8BD 0%, #EFCC8D 100%);">
+              style="background: radial-gradient( 0% 0% at 0% 0%, #FEE8BD 0%, #EFCC8D 100%);" @click="handleVip">
               <text class="mr-4 text-24 text-[#532F12] font-bold leading-23">开通会员</text>
               <uv-icon name="arrow-right" size="13" color="#532F12" />
             </view>
@@ -50,6 +50,7 @@ const avatar = computed(() => userStore.avatar);
 const nickName = computed(() => userStore.nickName);
 const phonenumber = computed(() => userStore.anonymousPhoneNumber);
 const isVip = computed(() => userStore.isVip);
+const isStudent = computed(() => userStore.isStudent);
 const isLogin = computed(() => userStore.isLogin);
 const isExperienceVip = computed(() => userStore.isExperienceVip);
 const vipInfo = computed(() => userStore.vipInfo);
@@ -81,6 +82,10 @@ const handleHeaderClick = async () => {
     }, 500)
   }
 }
+const handleVip = () => {
+  transferTo('/pagesSystem/pages/card-verify/card-verify')
+}
+
 const handleSettingClick = async () => {
   transferTo('/pagesOther/pages/personal-center/setting/setting');
 }

+ 5 - 8
src/pagesStudy/components/practice-table.vue

@@ -114,14 +114,12 @@
   </view>
 </template>
 <script lang="ts" setup>
-import { nextTick } from 'vue';
 import { TableColumnConfig } from '@/types';
 import ieEchart from './ie-echart/ie-echart.vue';
 import { useCalendar } from '@/composables/useCalendar';
-import { getPlanStudyRecord } from '@/api/modules/study';
-import * as Study from '@/types';
+
 const props = defineProps<{
-  studentId: number;
+  recordId?: number;
 }>();
 // 使用 useCalendar composable
 const {
@@ -305,7 +303,7 @@ const options2 = computed(() => {
         label: { show: false },
         data: [
           {
-            value: 75,
+            value: accuracy,
             itemStyle: {
               color: {
                 type: 'linear',
@@ -321,7 +319,7 @@ const options2 = computed(() => {
             }
           },
           {
-            value: 35,
+            value: 100 - accuracy,
             itemStyle: { color: '#EBF9FF' }
           }
         ]
@@ -451,12 +449,11 @@ const handleOpenCalendar = () => {
 onMounted(async () => {
   // 先初始化默认选中
   initializeDefaultSelection();
-  initCalendar(props.studentId);
   // 显示全屏loading
   uni.$ie.showLoading();
   try {
     // 初始化日历
-    // await initCalendar(props.studentId);
+    await initCalendar(props.recordId);
   } finally {
     // 隐藏loading
     uni.$ie.hideLoading();

+ 10 - 11
src/pagesStudy/components/teacher-class-view.vue

@@ -15,21 +15,20 @@
 </template>
 <script lang="ts" setup>
 import { TeachClass } from '@/types/study';
-const classId = ref<number>(0);
+import { getTeachClassList } from '@/api/modules/study';
+const classId = ref<number | null>(null);
 const classList = ref<TeachClass[]>([]);
 const selectedClass = ref<TeachClass>();
 const pickerRef = ref();
 const loadData = async () => {
-  // const res = await getClassHistory();
-  // 模拟数据
-  classList.value = [
-    { classId: 1, schoolId: 1, year: 2025, name: '班级1' },
-    { classId: 2, schoolId: 1, year: 2025, name: '班级2' },
-    { classId: 3, schoolId: 1, year: 2025, name: '班级3' },
-  ];
-  // 默认选中第一个
-  classId.value = classList.value[0].classId;
-  selectedClass.value = classList.value[0];
+  try {
+    const { data } = await getTeachClassList();
+    classList.value = data;
+    classId.value = classList.value[0].classId;
+    selectedClass.value = classList.value[0];
+  } catch (error) {
+    console.log(error);
+  }
 }
 onMounted(() => {
   loadData();

+ 2 - 8
src/pagesStudy/components/video-table.vue

@@ -11,7 +11,7 @@
       </view>
     </view>
     <view class="mx-30">
-      <ie-table :table-columns="tableColumns" :data="data" @rowClick="handleRowClick">
+      <ie-table :table-columns="tableColumns" :data="data">
         <template #name="{ item }">
           <view class="flex items-center justify-center">
             <ie-image :src="item.avatar" class="w-60 h-60 bg-back" :round="999" />
@@ -29,7 +29,7 @@
 import { TableColumnConfig } from '@/types';
 import * as Study from '@/types/study';
 const props = defineProps<{
-  data: Study.VideoStudyRecord;
+  data: Study.VideoStudy;
 }>();
 
 const stat = computed(() => [
@@ -65,11 +65,5 @@ const tableColumns = ref<TableColumnConfig[]>([
   }
 ])
 const data = computed(() => props.data.list || [])
-const emit = defineEmits<{
-  rowClick: [row: Study.StudentVideoRecord]
-}>();
-const handleRowClick = (row: Study.StudentVideoRecord) => {
-  emit('rowClick', row);
-}
 </script>
 <style lang="scss" scoped></style>

+ 83 - 0
src/pagesStudy/pages/study-exam-simulated-class/study-exam-simulated-class.vue

@@ -0,0 +1,83 @@
+<template>
+  <ie-page bg-color="#F6F8FA" :fix-height="true">
+    <ie-navbar :title="pageTitle" />
+    <view class="h-20"></view>
+    <view class="bg-white flex-1 min-h-1">
+      <view class="pt-30 px-30 text-30 text-fore-title font-bold">{{ prevData.name }}班的做卷详情</view>
+      <view class="p-30">
+        <ie-table :tableColumns="tableColumns" :data="tableData" :cellStyle="cellStyle">
+          <template #name="{ item }">
+            <text class="font-bold">{{ item.name }}</text>
+          </template>
+          <template #score="{ item }">
+            <text class="font-bold">{{ item.score }}</text>
+          </template>
+          <template #action="{ item }">
+            <text class="text-30 text-primary font-bold" @click="handleRowClick(item)">查看</text>
+          </template>
+        </ie-table>
+      </view>
+    </view>
+  </ie-page>
+</template>
+<script lang="ts" setup>
+import { useTransferPage } from '@/hooks/useTransferPage';
+import { Study, TableColumnConfig } from '@/types';
+import { getStudentExamRecord } from '@/api/modules/study';
+import { CSSProperties } from 'vue';
+
+const { prevData, transferTo } = useTransferPage();
+const cellStyle: CSSProperties = {
+  padding: '30rpx 20rpx'
+}
+const tableData = ref<Study.StudentExamRecord[]>([]);
+const tableColumns: TableColumnConfig[] = [
+  {
+    prop: 'name',
+    label: '状态',
+    flex: 1,
+    slot: 'name',
+    headerAlign: 'center',
+    align: 'center'
+  },
+  {
+    prop: 'total',
+    label: '做卷数量',
+    flex: 1,
+  },
+  {
+    prop: 'action',
+    label: '操作',
+    flex: 1,
+    slot: 'action'
+  }
+];
+
+const pageTitle = computed(() => {
+  return '模考-班级详情';
+});
+
+const handleRowClick = (row: Study.StudentExamRecord) => {
+  transferTo('/pagesStudy/pages/study-exam-simulated-student/study-exam-simulated-student', {
+    data: {
+      studentId: row.id,
+      name: row.name
+    }
+  });
+}
+const loadData = async () => {
+  uni.$ie.showLoading();
+  try {
+    const res = await getStudentExamRecord({
+      classId: prevData.value.classId,
+    });
+    tableData.value = res.data;
+  } finally {
+    uni.$ie.hideLoading();
+  }
+}
+onLoad(() => {
+  loadData();
+});
+</script>
+<style lang="scss" scoped></style>

+ 84 - 0
src/pagesStudy/pages/study-exam-simulated-detail/study-exam-simulated-detail.vue

@@ -0,0 +1,84 @@
+<template>
+  <ie-page bg-color="#F6F8FA" :fix-height="true">
+    <ie-navbar :title="pageTitle" />
+    <view class="h-20"></view>
+    <view class="bg-white flex-1 min-h-1">
+      <view class="pt-30 px-30 text-30 text-fore-title font-bold">{{ prevData.name }}的{{ prevData.subjectName }}试卷详情</view>
+      <view class="p-30">
+        <ie-table :tableColumns="tableColumns" :data="tableData" :cellStyle="cellStyle">
+          <template #name="{ item }">
+            <text class="font-bold">{{ item.name }}</text>
+          </template>
+          <template #total="{ item }">
+            <text class="font-bold">{{ item.total }}</text>
+          </template>
+          <template #rate="{ item }">
+            <text :class="[item.rate < 70 ? 'text-danger' : 'text-fore-title']">{{ item.rate }}%</text>
+          </template>
+        </ie-table>
+      </view>
+    </view>
+  </ie-page>
+</template>
+<script lang="ts" setup>
+import { useTransferPage } from '@/hooks/useTransferPage';
+import { Study, TableColumnConfig } from '@/types';
+import { getStudentExamSubjectDetail } from '@/api/modules/study';
+import { CSSProperties } from 'vue';
+
+const { prevData, transferTo } = useTransferPage();
+const cellStyle: CSSProperties = {
+  padding: '30rpx 20rpx'
+}
+const tableData = ref<Study.StudentExamRecord[]>([]);
+const tableColumns: TableColumnConfig[] = [
+  {
+    prop: 'name',
+    label: '状态',
+    flex: 1,
+    slot: 'name',
+    headerAlign: 'center',
+    align: 'center'
+  },
+  {
+    prop: 'total',
+    label: '做题量',
+    flex: 1,
+    slot: 'total'
+  },
+  {
+    prop: 'rate',
+    label: '正确率',
+    flex: 1,
+    slot: 'rate'
+  }
+];
+
+const pageTitle = computed(() => {
+  return '模考-试卷详情';
+});
+
+const handleRowClick = (row: Study.StudentExamRecord) => {
+  transferTo('/pagesStudy/pages/study-exam-simulated-detail/study-exam-simulated-detail', {
+    data: {
+      studentId: row.id,
+      name: row.name
+    }
+  });
+}
+const loadData = async () => {
+  uni.$ie.showLoading();
+  try {
+    const res = await getStudentExamSubjectDetail({
+      examineeId: prevData.value.examineeId,
+    });
+    tableData.value = res.data;
+  } finally {
+    uni.$ie.hideLoading();
+  }
+}
+onLoad(() => {
+  loadData();
+});
+</script>
+<style lang="scss" scoped></style>

+ 85 - 0
src/pagesStudy/pages/study-exam-simulated-student/study-exam-simulated-student.vue

@@ -0,0 +1,85 @@
+<template>
+  <ie-page bg-color="#F6F8FA" :fix-height="true">
+    <ie-navbar :title="pageTitle" />
+    <view class="h-20"></view>
+    <view class="bg-white flex-1 min-h-1">
+      <view class="pt-30 px-30 text-30 text-fore-title font-bold">{{ prevData.name }}的做卷详情</view>
+      <view class="p-30">
+        <ie-table :tableColumns="tableColumns" :data="tableData" :cellStyle="cellStyle">
+          <template #name="{ item }">
+            <text class="font-bold">{{ item.name }}</text>
+          </template>
+          <template #total="{ item }">
+            <text class="font-bold">{{ item.value }}/{{ item.total }}</text>
+          </template>
+          <template #action="{ item }">
+            <text class="text-30 text-primary font-bold" @click="handleRowClick(item)">查看</text>
+          </template>
+        </ie-table>
+      </view>
+    </view>
+  </ie-page>
+</template>
+<script lang="ts" setup>
+import { useTransferPage } from '@/hooks/useTransferPage';
+import { Study, TableColumnConfig } from '@/types';
+import { getStudentExamSubjectRecord } from '@/api/modules/study';
+import { CSSProperties } from 'vue';
+
+const { prevData, transferTo } = useTransferPage();
+const cellStyle: CSSProperties = {
+  padding: '30rpx 20rpx'
+}
+const tableData = ref<Study.StudentExamRecord[]>([]);
+const tableColumns: TableColumnConfig[] = [
+  {
+    prop: 'name',
+    label: '状态',
+    flex: 1,
+    slot: 'name',
+    headerAlign: 'center',
+    align: 'center'
+  },
+  {
+    prop: 'total',
+    label: '得分/总分',
+    flex: 1,
+    slot: 'total'
+  },
+  {
+    prop: 'action',
+    label: '操作',
+    flex: 1,
+    slot: 'action'
+  }
+];
+
+const pageTitle = computed(() => {
+  return '模考-做卷详情';
+});
+
+const handleRowClick = (row: Study.StudentExamRecord) => {
+  transferTo('/pagesStudy/pages/study-exam-simulated-detail/study-exam-simulated-detail', {
+    data: {
+      examineeId: row.id,
+      name: prevData.value.name,
+      subjectName: row.name
+    }
+  });
+}
+const loadData = async () => {
+  uni.$ie.showLoading();
+  try {
+    const res = await getStudentExamSubjectRecord({
+      studentId: prevData.value.studentId,
+    });
+    tableData.value = res.data;
+  } finally {
+    uni.$ie.hideLoading();
+  }
+}
+onLoad(() => {
+  loadData();
+});
+</script>
+<style lang="scss" scoped></style>

+ 232 - 0
src/pagesStudy/pages/study-history/components/exam-history-paperwork.vue

@@ -0,0 +1,232 @@
+<template>
+  <view>
+    <view class="w-full flex mb-30 justify-between">
+      <view class="">
+        <ie-picker ref="pickerRef" v-model="queryForm.buildStatus" :list="buildTypeList" placeholder="类型" title="类型"
+          icon="arrow-down" key-label="name" key-value="value" :fontSize="28"
+          :placeholder-style="placeholderStyle"></ie-picker>
+      </view>
+      <view class="">
+        <ie-picker ref="pickerRef" v-model="queryForm.batchId" :list="batchList" placeholder="批次" title="批次"
+          icon="arrow-down" key-label="name" key-value="batchId" :fontSize="28"
+          :placeholder-style="placeholderStyle"></ie-picker>
+      </view>
+      <view class="">
+        <ie-picker ref="pickerRef" v-model="queryForm.classId" :list="classList" placeholder="班级" title="班级"
+          icon="arrow-down" key-label="name" key-value="classId" :fontSize="28"
+          :placeholder-style="placeholderStyle"></ie-picker>
+      </view>
+      <view class="max-w-180">
+        <ie-picker ref="pickerRef" v-model="queryForm.subjectId" :list="subjectList" placeholder="科目" title="科目"
+          icon="arrow-down" key-label="subjectName" key-value="subjectId" :fontSize="28"
+          :placeholder-style="placeholderStyle"></ie-picker>
+      </view>
+    </view>
+    <ie-table :tableColumns="tableColumns" :data="tableData" :cellStyle="cellStyle">
+      <template #name="{ item }">
+        <text class="font-bold">{{ getStatusText(item.buildStatus) }}</text>
+      </template>
+      <template #total="{ item }">
+        <text class="font-bold">{{ item.count || 0 }}</text>
+      </template>
+      <template #status="{ item }">
+        <text class="text-30 text-primary font-bold" @click="handleRowClick(item)">查看</text>
+      </template>
+    </ie-table>
+  </view>
+  <ie-popup :title="popupTitle" ref="popupRef" mode="bottom" :showToolbar="false">
+    <view class="p-30 relative">
+      <view class="text-center text-30 font-bold">{{ popupTitle }}</view>
+      <uv-icon name="close" size="18" color="#333" class="absolute top-34 right-20" @click="popupRef.close()" />
+    </view>
+    <view class="h-[400px]">
+      <scroll-view scroll-y class="h-full">
+        <view class="px-20 py-4">
+          <ie-table :tableColumns="tableColumns2" :data="tableData2" :cellStyle="cellStyle" @rowClick="handleRowClick">
+            <template #status="{ item }">
+              <text class="text-30 text-primary font-bold" @click="handleRowClick(item)">查看</text>
+            </template>
+          </ie-table>
+        </view>
+      </scroll-view>
+    </view>
+  </ie-popup>
+</template>
+<script lang="ts" setup>
+import { Study, TableColumnConfig } from '@/types';
+import { CSSProperties } from 'vue';
+import { getBatchList, getTeachClassList, getTeacherSubjectList, getTeacherTestRecord, getTeacherTestRecordDetail, getTeacherTestRecordCondition } from '@/api/modules/study';
+
+const cellStyle: CSSProperties = {
+  padding: '30rpx 20rpx'
+}
+const queryForm = ref<Partial<Study.PaperWorkRecordQuery>>({});
+const buildTypeList = ref([
+  {
+    name: '未定向未组卷',
+    value: 10
+  },
+  {
+    name: '未组卷',
+    value: 20
+  },
+  {
+    name: '组卷未完成',
+    value: 30
+  },
+  {
+    name: '组卷已完成',
+    value: 40
+  }
+]);
+const popupTitle = ref('');
+const classList = ref<Study.TeachClass[]>([]);
+const batchList = ref<Study.Batch[]>([]);
+const subjectList = ref<Study.Subject[]>([]);
+const pickerRef = ref();
+const placeholderStyle = {
+  color: '#1A1A1A'
+}
+const tableData = ref<Study.PaperWorkRecord[]>([]);
+const tableColumns: TableColumnConfig[] = [
+  {
+    prop: 'name',
+    label: '状态',
+    flex: 1,
+    slot: 'name',
+    headerAlign: 'center',
+    align: 'center'
+  },
+  {
+    prop: 'total',
+    label: '人数',
+    flex: 1,
+    slot: 'total'
+  },
+  {
+    prop: 'status',
+    label: '操作',
+    flex: 1,
+    slot: 'status'
+  }
+];
+const tableColumns2: TableColumnConfig[] = [
+  {
+    prop: 'className',
+    label: '班级',
+    flex: 1,
+    headerAlign: 'center',
+    align: 'center'
+  },
+  {
+    prop: 'nickName',
+    label: '姓名',
+    flex: 1
+  },
+  {
+    prop: 'state',
+    label: '状态',
+    flex: 1,
+    headerAlign: 'right',
+    align: 'right',
+  }
+];
+const tableData2 = ref<Study.PaperWorkRecordDetail[]>([]);
+
+const getStatusText = (status: number | null) => {
+  if (!status) {
+    return '总人数';
+  }
+  const statusMap = {
+    10: '未定向未组卷',
+    20: '未组卷',
+    30: '组卷未完成',
+    40: '组卷已完成'
+  }
+  return statusMap[status as keyof typeof statusMap];
+}
+const popupRef = ref();
+const handleRowClick = async (row: Study.PaperWorkRecord) => {
+  uni.$ie.showLoading();
+  try {
+    const params = {
+      ...queryForm.value
+    } as Study.PaperWorkRecordQuery;
+    if (row.buildStatus !== null) {
+      params.buildStatus = row.buildStatus;
+    } else {
+      delete (params as any).buildStatus;
+    }
+    await getTeacherTestRecordDetail(params).then(res => {
+      tableData2.value = res.data;
+    });
+  } finally {
+    uni.$ie.hideLoading();
+  }
+  popupTitle.value = getStatusText(row.buildStatus);
+  popupRef.value.open();
+}
+
+const loadData = async () => {
+  const queryBatch = getBatchList({}).then(res => {
+    batchList.value = res.data;
+  });
+  const queryClass = getTeachClassList({}).then(res => {
+    classList.value = res.data;
+  });
+  const querySubject = getTeacherSubjectList({}).then(res => {
+    subjectList.value = res.data;
+  });
+  await Promise.all([queryBatch, queryClass, querySubject]);
+  const queryCondition = getTeacherTestRecordCondition({}).then(res => {
+    const { buildStatus, batchId, classId, subjectId } = res.data;
+    queryForm.value = {
+      buildStatus: buildStatus || buildTypeList.value[0].value,
+      batchId: batchId || batchList.value[0].batchId,
+      classId: classId || classList.value[0].classId,
+      subjectId: subjectId || subjectList.value[0].subjectId,
+    };
+  });
+  await Promise.all([queryCondition]);
+}
+
+const loadTestRecord = () => {
+  const params = {} as Study.PaperWorkRecordQuery;
+  const { buildStatus, batchId, classId, subjectId } = queryForm.value;
+  if (buildStatus !== undefined && buildStatus !== null) {
+    params.buildStatus = buildStatus;
+  }
+  if (batchId !== undefined && batchId !== null) {
+    params.batchId = batchId;
+  }
+  if (classId !== undefined && classId !== null) {
+    params.classId = classId;
+  }
+  if (subjectId !== undefined && subjectId !== null) {
+    params.subjectId = subjectId;
+  }
+  uni.$ie.showLoading();
+  getTeacherTestRecord(params).then(res => {
+    // 根据类型过滤,保留总人数和指定类型的,没有类型时,显示所有类型
+    if (!queryForm.value.buildStatus) {
+      tableData.value = res.data;
+    } else {
+      tableData.value = res.data.filter(item => item.buildStatus === null || Number(item.buildStatus) === Number(queryForm.value.buildStatus));
+    }
+  }).finally(() => {
+    uni.$ie.hideLoading();
+  });
+}
+
+watch(() => queryForm.value, () => {
+  loadTestRecord();
+}, {
+  deep: true,
+  immediate: false
+});
+
+onMounted(() => {
+  loadData();
+});
+</script>
+<style lang="scss" scoped></style>

+ 74 - 0
src/pagesStudy/pages/study-history/components/exam-history-simulated.vue

@@ -0,0 +1,74 @@
+<template>
+  <view>
+    <ie-table :tableColumns="tableColumns" :data="tableData" :cellStyle="cellStyle">
+      <template #name="{ item }: { item: StudentExamRecord }">
+        <text class="font-bold">{{ item.name }}</text>
+      </template>
+      <template #total="{ item }: { item: StudentExamRecord }">
+        <text class="font-bold">{{ item.value }}/{{ item.total }}</text>
+      </template>
+      <template #action="{ item }: { item: StudentExamRecord }">
+        <text class="text-30 text-primary font-bold" @click="handleRowClick(item)">查看</text>
+      </template>
+    </ie-table>
+  </view>
+</template>
+<script lang="ts" setup>
+import { getClassExamRecord } from '@/api/modules/study';
+import { Study, TableColumnConfig, Transfer } from '@/types';
+import { StudentExamRecord } from '@/types/study';
+import { CSSProperties } from 'vue';
+import { useTransferPage } from '@/hooks/useTransferPage';
+const { transferTo } = useTransferPage();
+
+const cellStyle: CSSProperties = {
+  padding: '30rpx 20rpx'
+}
+
+const tableData = ref<StudentExamRecord[]>([]);
+const tableColumns: TableColumnConfig[] = [
+  {
+    prop: 'name',
+    label: '班级',
+    flex: 1,
+    slot: 'name',
+    headerAlign: 'center',
+    align: 'center'
+  },
+  {
+    prop: 'total',
+    label: '做卷情况',
+    flex: 1,
+    slot: 'total'
+  },
+  {
+    prop: 'action',
+    label: '操作',
+    flex: 1,
+    slot: 'action'
+  }
+];
+
+
+const handleRowClick = (row: StudentExamRecord) => {
+  transferTo('/pagesStudy/pages/study-exam-simulated-class/study-exam-simulated-class', {
+    data: {
+      classId: row.id,
+      name: row.name
+    }
+  });
+}
+
+const loadData = async () => {
+  uni.$ie.showLoading();
+  await getClassExamRecord({}).then(res => {
+    tableData.value = res.data;
+  });
+  uni.$ie.hideLoading();
+}
+
+onMounted(() => {
+  loadData();
+})
+</script>
+<style lang="scss" scoped></style>

+ 4 - 166
src/pagesStudy/pages/study-history/components/exam-history-teacher.vue

@@ -1,51 +1,12 @@
 <template>
   <view class="p-30 pt-50">
-    <view class="w-fit flex gap-x-50">
-      <ie-picker ref="pickerRef" v-model="classId" :list="classList" placeholder="选择批次" title="选择批次" icon="arrow-down"
-        key-label="name" key-value="classId" @change="handleClassChange"
-        :placeholder-style="placeholderStyle"></ie-picker>
-
-      <ie-picker ref="pickerRef" v-model="classId" :list="classList" placeholder="选择班级" title="选择班级" icon="arrow-down"
-        key-label="name" key-value="classId" @change="handleClassChange"
-        :placeholder-style="placeholderStyle"></ie-picker>
-    </view>
-    <view class="mt-32 py-38 bg-back border border-solid border-border rounded-10 flex">
-      <view class="flex-1 text-center">
-        <view class="text-40 text-primary font-bold leading-30">64</view>
-        <view class="mt-24 text-28 text-fore-light leading-28">总人数</view>
-      </view>
-      <view class="flex-1 text-center">
-        <view class="text-40 text-[#22C55E] font-bold leading-30">27</view>
-        <view class="mt-24 text-28 text-fore-light leading-28">完成</view>
-      </view>
-      <view class="flex-1 text-center">
-        <view class="text-40 text-[#F59E0B] font-bold leading-30">10</view>
-        <view class="mt-24 text-28 text-fore-light leading-28">未完成</view>
-      </view>
-      <view class="flex-1 text-center">
-        <view class="text-40 text-[#F59E0B] font-bold leading-30">9</view>
-        <view class="mt-24 text-28 text-fore-light leading-28">未发送</view>
-      </view>
-    </view>
-    <view class="mt-70">
-      <ie-table :tableColumns="tableColumns" :data="tableData" :cellStyle="cellStyle" @rowClick="handleRowClick">
-        <template #name="{ item }: { item: StudentExamRecord }">
-          <text class="font-bold">{{ item.name }}</text>
-        </template>
-        <template #score="{ item }: { item: StudentExamRecord }">
-          <text class="font-bold">{{ item.score }}</text>
-        </template>
-        <template #status="{ item }: { item: StudentExamRecord }">
-          <text :class="getStatusClass(item.status)">{{ getStatusText(item.status) }}</text>
-        </template>
-      </ie-table>
-    </view>
+    <exam-history-simulated v-if="examType === 'simulated'" />
+    <exam-history-paperwork v-else />
   </view>
 </template>
 <script lang="ts" setup>
-import { TableColumnConfig } from '@/types';
-import { StudentExamRecord, TeachClass } from '@/types/study';
-import { CSSProperties } from 'vue';
+import examHistorySimulated from './exam-history-simulated.vue';
+import examHistoryPaperwork from './exam-history-paperwork.vue';
 
 const props = defineProps({
   examType: {
@@ -53,128 +14,5 @@ const props = defineProps({
     default: 'test'
   }
 });
-const classId = ref<number>(0);
-const classList = ref<TeachClass[]>([]);
-const pickerRef = ref();
-const placeholderStyle = {
-  color: '#1A1A1A'
-}
-const tableColumns: TableColumnConfig[] = [
-  {
-    prop: 'name',
-    label: '姓名',
-    flex: 1,
-    slot: 'name',
-    headerAlign: 'center',
-    align: 'center'
-  },
-  {
-    prop: 'score',
-    label: '得分',
-    flex: 1,
-    slot: 'score'
-  },
-  {
-    prop: 'status',
-    label: '状态',
-    flex: 1,
-    slot: 'status'
-  }
-]
-const tableData = ref<StudentExamRecord[]>([
-  {
-    id: 1,
-    name: '张三',
-    score: 100,
-    status: 1
-  },
-  {
-    id: 2,
-    name: '李四',
-    score: 90,
-    status: 2
-  },
-  {
-    id: 3,
-    name: '王五',
-    score: 80,
-    status: 1
-  },
-  {
-    id: 4,
-    name: '赵六',
-    score: 70,
-    status: 1
-  },
-  {
-    id: 5,
-    name: '孙七',
-    score: 60,
-    status: 2
-  },
-  {
-    id: 6,
-    name: '周八',
-    score: 50,
-    status: 2
-  },
-  {
-    id: 7,
-    name: '吴九',
-    score: 40,
-    status: 1
-  }
-])
-const cellStyle: CSSProperties = {
-  padding: '30rpx 20rpx'
-}
-const handleClassChange = (value: number, selectedItem: TeachClass) => {
-  classId.value = value;
-}
-
-// 处理表格行点击事件
-const handleRowClick = (row: StudentExamRecord) => {
-  console.log('点击了行:', row);
-  // 这里可以添加跳转到详情页或其他逻辑
-}
-
-// 获取状态文本
-const getStatusText = (status: number) => {
-  const statusMap = {
-    1: '已完成',
-    2: '未完成',
-    3: '未发送'
-  };
-  return statusMap[status as keyof typeof statusMap] || '未知';
-}
-
-// 获取状态样式类
-const getStatusClass = (status: number) => {
-  const classMap = {
-    1: 'text-[#22C55E] font-bold',
-    2: 'text-[#F59E0B] font-bold',
-    3: 'text-[#6B7280] font-bold'
-  };
-  return classMap[status as keyof typeof classMap] || 'text-gray-500';
-}
-
-const loadData = async () => {
-  // const res = await getClassHistory();
-  // console.log(res);
-  uni.$ie.showLoading();
-  setTimeout(() => {
-    uni.$ie.hideLoading();
-  }, 1000);
-}
-const loadClassList = async () => {
-  // const res = await getClassList();
-  // console.log(res);
-}
-
-watch(() => props.examType, (newVal) => {
-  loadData();
-}, {
-  immediate: true
-});
 </script>
 <style lang="scss" scoped></style>

+ 21 - 29
src/pagesStudy/pages/study-history/components/knowledge-history-teacher.vue

@@ -1,11 +1,11 @@
 <template>
   <view class="mt-40 flex items-center justify-end gap-x-10">
     <text class="text-24 text-fore-light">平均正确率</text>
-    <text class="text-30 text-fore-title font-bold">80%</text>
+    <text class="text-30 text-fore-title font-bold">{{ averageRate.toFixed(1) }}%</text>
   </view>
   <view class="mt-10">
-    <progress :percent="80" stroke-width="12" activeColor="#68D119" backgroundColor="#F2F2F2" :border-radius="8"
-      class="custom-progress" />
+    <progress :percent="averageRate" stroke-width="12" activeColor="#68D119" backgroundColor="#F2F2F2"
+      :border-radius="8" class="custom-progress" />
   </view>
   <view class="mt-76">
     <student-stat-table :teach-class="teachClass" :data="studentStatData" @rowClick="handleRowClick" />
@@ -14,7 +14,8 @@
 <script lang="ts" setup>
 import { OPEN_KNOWLEDGE_DETAIL } from '@/types/injectionSymbols';
 import studentStatTable from './student-stat-table.vue';
-import { StudentStat, TeachClass } from '@/types/study';
+import { StudentPlanStudyRecord, TeachClass } from '@/types/study';
+import { getClassKnowledgeRecord } from '@/api/modules/study';
 const openKnowledgeDetail = inject(OPEN_KNOWLEDGE_DETAIL);
 const props = defineProps({
   teachClass: {
@@ -22,38 +23,29 @@ const props = defineProps({
     default: () => ({})
   }
 });
-const studentStatData = ref<StudentStat[]>([
-  {
-    id: 1,
-    name: '张三',
-    questionNum: 10,
-    dateNum: 10,
-    rate: 80
-  },
-  {
-    id: 2,
-    name: '李四',
-    questionNum: 10,
-    dateNum: 10,
-    rate: 80
-  }
-]);
+const studentStatData = ref<StudentPlanStudyRecord[]>([]);
+const averageRate = ref(0);
+
+const loadData = async () => {
+  getClassKnowledgeRecord({
+    classId: props.teachClass.classId,
+  }).then(res => {
+    studentStatData.value = res.data.list;
+    averageRate.value = res.data.rate;
+  })
+}
+
+const handleRowClick = (row: StudentPlanStudyRecord) => {
+  openKnowledgeDetail?.(row.id, row.name);
+}
+
 watch(() => props.teachClass, (newVal) => {
   if (newVal.classId) {
-    console.log('新的班级', newVal);
     loadData();
   }
 }, {
   deep: true,
   immediate: true
 });
-
-const loadData = async () => {
-  // const res = await getStudentStat(props.teachClass.classId);
-  // console.log(res);
-}
-const handleRowClick = (row: StudentStat) => {
-  openKnowledgeDetail?.(row.id, row.name);
-}
 </script>
 <style lang="scss" scoped></style>

+ 1 - 1
src/pagesStudy/pages/study-history/components/knowledge-history.vue

@@ -21,7 +21,7 @@ const { isStudent } = storeToRefs(useUserStore());
 const openKnowledgeDetail = (id: number, name: string) => {
   transferTo('/pagesStudy/pages/study-knowledge-detail/study-knowledge-detail', {
     data: {
-      studentId: id,
+      recordId: id,
       name: name
     }
   });

+ 1 - 4
src/pagesStudy/pages/study-history/components/practice-history-student.vue

@@ -1,12 +1,9 @@
 <template>
   <view>
-    <practice-table :student-id="studentId" />
+    <practice-table />
   </view>
 </template>
 <script lang="ts" setup>
 import practiceTable from '@/pagesStudy/components/practice-table.vue';
-import { useUserStore } from '@/store/userStore';
-const userStore = useUserStore();
-const studentId = ref(userStore.userInfo.userId);
 </script>
 <style lang="scss" scoped></style>

+ 25 - 29
src/pagesStudy/pages/study-history/components/practice-history-teacher.vue

@@ -1,56 +1,52 @@
 <template>
   <view>
-     <student-stat-table :teach-class="teachClass" :data="studentStatData" @row-click="handleRowClick" />
+    <student-stat-table :teach-class="teachClass" :data="studentStatData" @row-click="handleRowClick" />
   </view>
 </template>
 <script lang="ts" setup>
 import studentStatTable from './student-stat-table.vue';
-import { StudentStat, TeachClass } from '@/types/study';
+import { Study } from '@/types';
 import { OPEN_PRACTICE_DETAIL } from '@/types/injectionSymbols';
+import { getClassPlanStudyRecord } from '@/api/modules/study';
+
 const openPracticeDetail = inject(OPEN_PRACTICE_DETAIL);
 const props = defineProps({
   teachClass: {
-    type: Object as PropType<TeachClass>,
+    type: Object as PropType<Study.TeachClass>,
     default: () => ({})
   },
   currentType: {
     type: String,
     default: 'rate'
   },
-  currentTab: {
+  currentSort: {
     type: String,
     default: 'asc'
   }
 });
-const studentId = ref(0);
-const studentStatData = ref<StudentStat[]>([
-  {
-    id: 1,
-    name: '张三',
-    questionNum: 10,
-    dateNum: 10,
-    rate: 80
-  },
-  {
-    id: 2,
-    name: '李四',
-    questionNum: 10,
-    dateNum: 10,
-    rate: 80
-  }
-]);
+
+const studentStatData = ref<Study.StudentPracticeRecord[]>([]);
+
 const loadData = async () => {
-  console.log('loadData', props.teachClass, props.currentTab, props.currentType);
-  // const res = await getPracticeData(props.teachClass.classId);
-  // data.value = res.data;
+  uni.$ie.showLoading();
+  try {
+    const { rows } = await getClassPlanStudyRecord({
+      classId: props.teachClass.classId,
+      sortField: props.currentType,
+      asc: props.currentSort === 'asc'
+    });
+    studentStatData.value = rows;
+  } finally {
+    uni.$ie.hideLoading();
+  }
 }
 watchEffect(() => {
-  loadData();
+  if (props.teachClass.classId) {
+    loadData();
+  }
 });
-const handleRowClick = (row: StudentStat) => {
-  console.log('点击了行:', row);
+const handleRowClick = (row: Study.StudentPracticeRecord) => {
   openPracticeDetail?.(row.id, row.name);
 }
 </script>
-<style lang="scss" scoped>
-</style>
+<style lang="scss" scoped></style>

+ 8 - 8
src/pagesStudy/pages/study-history/components/practice-history.vue

@@ -9,10 +9,10 @@
           </view>
         </view>
         <view class="mt-30 w-fit mx-auto">
-          <ie-tab :options="tabList" v-model="currentTab" />
+          <ie-tab :options="tabList" v-model="currentSort" />
         </view>
         <view class="mt-30">
-          <practice-history-teacher :teach-class="teachClass" :current-type="currentType" :current-tab="currentTab" />
+          <practice-history-teacher :teach-class="teachClass" :current-type="currentType" :current-sort="currentSort" />
         </view>
       </template>
     </teacher-class-view>
@@ -34,14 +34,14 @@ const typeList = ref([
   },
   {
     label: '按刷题天数',
-    value: 'practiceDays'
+    value: 'time'
   },
   {
     label: '按刷题题量',
-    value: 'questionNum'
+    value: 'total'
   }
 ])
-const currentTab = ref('asc');
+const currentSort = ref('asc');
 const tabList = ref([
   {
     label: '升序',
@@ -56,11 +56,11 @@ const { isStudent } = storeToRefs(useUserStore());
 const handleTypeChange = (value: string) => {
   currentType.value = value;
 }
-const openPracticeDetail = (id: number, name: string) => {
+const openPracticeDetail = (recordId: number, name: string) => {
   transferTo('/pagesStudy/pages/study-practice-detail/study-practice-detail', {
     data: {
-      studentId: id,
-      name: name
+      recordId,
+      name
     }
   });
 }

+ 12 - 8
src/pagesStudy/pages/study-history/components/student-stat-table.vue

@@ -7,19 +7,22 @@
           <text class="ml-10 flex-1 min-w-1 ellipsis-1">{{ item.name }}</text>
         </view>
       </template>
+      <template #rate="{ item }">
+        <text :class="[item.rate < 70 ? 'text-danger' : 'text-fore-title']">{{ item.rate }}%</text>
+      </template>
     </ie-table>
   </view>
 </template>
 <script lang="ts" setup>
 import { TableColumnConfig, TableConfig } from '@/types';
-import { TeachClass, StudentStat } from '@/types/study';
+import { TeachClass, StudentPlanStudyRecord } from '@/types/study';
 const props = defineProps({
   teachClass: {
     type: Object as PropType<TeachClass>,
     default: () => ({})
   },
   data: {
-    type: Array as PropType<StudentStat[]>,
+    type: Array as PropType<StudentPlanStudyRecord[]>,
     default: () => []
   }
 });
@@ -43,28 +46,29 @@ const tableColumns: TableColumnConfig[] = [
     slot: 'name'
   },
   {
-    prop: 'questionNum',
+    prop: 'total',
     label: '题量',
     flex: 0.6
   },
   {
-    prop: 'dateNum',
+    prop: 'time',
     label: '天数',
     flex: 0.6
   },
   {
     prop: 'rate',
-    label: '正确率'
+    label: '正确率',
+    slot: 'rate'
   }
 ]
 const emit = defineEmits<{
-  rowClick: [row: StudentStat]
+  rowClick: [row: StudentPlanStudyRecord]
 }>();
-const handleRowClick = (row: StudentStat) => {
+const handleRowClick = (row: StudentPlanStudyRecord) => {
   emit('rowClick', row);
 }
 // const openKnowledgeDetail = inject(OPEN_KNOWLEDGE_DETAIL);
-// const handleRowClick = (row: StudentStat) => {
+// const handleRowClick = (row: StudentPlanStudyRecord) => {
 //   openKnowledgeDetail?.(row.id, row.name);
 // }
 </script>

+ 1 - 1
src/pagesStudy/pages/study-history/components/video-history-student.vue

@@ -7,7 +7,7 @@
 import videoTable from '@/pagesStudy/components/video-table.vue';
 import { getVideoStudyRecord } from '@/api/modules/study';
 import * as Study from '@/types/study';
-const dataList = ref<Study.VideoStudyRecord>({} as Study.VideoStudyRecord);
+const dataList = ref<Study.VideoStudy>({} as Study.VideoStudy);
 const loading = ref(false);
 const loadData = async () => {
   loading.value = true;

+ 44 - 14
src/pagesStudy/pages/study-history/components/video-history-teacher.vue

@@ -11,9 +11,22 @@
   </view>
 </template>
 <script lang="ts" setup>
-import { TableColumnConfig } from '@/types';
-import { StudentVideoStat } from '@/types/study';
+import { Study, TableColumnConfig } from '@/types';
+import { StudentVideoRecord } from '@/types/study';
 import { OPEN_VIDEO_DETAIL } from '@/types/injectionSymbols';
+import { getClassVideoStudyRecord } from '@/api/modules/study';
+
+const props = defineProps({
+  teachClass: {
+    type: Object as PropType<Study.TeachClass>,
+    default: () => ({})
+  },
+  currentSort: {
+    type: String,
+    default: 'asc'
+  }
+});
+
 const openVideoDetail = inject(OPEN_VIDEO_DETAIL);
 const tableColumns = ref<TableColumnConfig[]>([
   {
@@ -28,27 +41,44 @@ const tableColumns = ref<TableColumnConfig[]>([
     flex: 1
   },
   {
-    prop: 'videoCount',
+    prop: 'total',
     label: '课时',
     flex: 1
   },
   {
-    prop: 'duration',
+    prop: 'value',
     label: '时长(分钟)',
     flex: 1
   }
 ])
-const data = ref<StudentVideoStat[]>([
-  {
-    id: 1,
-    name: '张三',
-    videoCount: 10,
-    duration: 100
-  }
-])
-const handleRowClick = (row: StudentVideoStat) => {
-  console.log('点击了行:', row);
+const data = ref<StudentVideoRecord[]>([])
+const handleRowClick = (row: StudentVideoRecord) => {
   openVideoDetail?.(row.id, row.name);
 }
+
+const loadData = async () => {
+  uni.$ie.showLoading();
+  try {
+    const { rows } = await getClassVideoStudyRecord({
+      classId: props.teachClass.classId,
+      asc: props.currentSort === 'asc'
+    });
+    data.value = rows.map(item => {
+      return {
+        ...item,
+        value: Math.round(item.value / 60)
+      }
+    });
+  } finally {
+    uni.$ie.hideLoading();
+  }
+}
+
+watchEffect(() => {
+  if (props.teachClass.classId) {
+    loadData();
+  }
+});
+
 </script>
 <style lang="scss" scoped></style>

+ 6 - 6
src/pagesStudy/pages/study-history/components/video-history.vue

@@ -4,9 +4,9 @@
     <teacher-class-view v-else>
       <template #default="{ teachClass }">
         <view class="mt-30 w-fit mx-auto">
-          <ie-tab :options="tabList" v-model="currentTab" />
+          <ie-tab :options="tabList" v-model="currentSort" />
         </view>
-        <video-history-teacher :teach-class="teachClass" />
+        <video-history-teacher :teach-class="teachClass" :current-sort="currentSort" />
       </template>
     </teacher-class-view>
   </view>
@@ -20,7 +20,7 @@ import { useTransferPage } from '@/hooks/useTransferPage';
 import { useUserStore } from '@/store/userStore';
 const { transferTo } = useTransferPage();
 const { isStudent } = storeToRefs(useUserStore());
-const currentTab = ref('asc');
+const currentSort = ref('asc');
 const tabList = ref([
   {
     label: '升序',
@@ -31,11 +31,11 @@ const tabList = ref([
     value: 'desc'
   }
 ])
-const openVideoDetail = (id: number, name: string) => {
+const openVideoDetail = (recordId: number, name: string) => {
   transferTo('/pagesStudy/pages/study-video-detail/study-video-detail', {
     data: {
-      studentId: id,
-      name: name
+      recordId,
+      name
     }
   });
 }

+ 24 - 3
src/pagesStudy/pages/study-knowledge-detail/study-knowledge-detail.vue

@@ -3,8 +3,8 @@
     <ie-navbar :title="pageTitle" />
     <view class="h-20"></view>
     <view class="bg-white flex-1 min-h-1">
-      <view class="pt-32 px-30 text-30 text-fore-title font-bold">{{ prevData.name }}的知识点记录</view>
-      <knowledge-table :student-id="studentId" />
+      <view class="pt-30 px-30 text-30 text-fore-title font-bold">{{ prevData.name }}的知识点记录</view>
+      <knowledge-table :data="tableList" />
     </view>
   </ie-page>
 </template>
@@ -12,9 +12,13 @@
 import knowledgeTable from '@/pagesStudy/components/knowledge-table.vue';
 import { useTransferPage } from '@/hooks/useTransferPage';
 import { useScroll } from '@/hooks/useScroll';
+import { getStudentKnowledgeRecord } from '@/api/modules/study';
+import { Study } from '@/types';
+
 const { prevData } = useTransferPage();
-const studentId = ref(prevData.value.studentId);
+const recordId = ref();
 const { scrollTop } = useScroll();
+const tableList = ref<Study.KnowledgeRecord[]>([]);
 
 const pageTitle = computed(() => {
   if (scrollTop.value > 45) {
@@ -22,5 +26,22 @@ const pageTitle = computed(() => {
   }
   return '学生知识点详情';
 });
+
+const loadData = async () => {
+  uni.$ie.showLoading();
+  try {
+    const { rows } = await getStudentKnowledgeRecord({
+      recordId: recordId.value,
+    });
+    tableList.value = rows;
+  } finally {
+    uni.$ie.hideLoading();
+  }
+
+}
+onLoad(() => {
+  recordId.value = prevData.value.recordId;
+  loadData();
+});
 </script>
 <style lang="scss" scoped></style>

+ 2 - 2
src/pagesStudy/pages/study-practice-detail/study-practice-detail.vue

@@ -4,7 +4,7 @@
     <view class="h-20"></view>
     <view class="bg-white flex-1 min-h-1">
       <view class="pt-32 px-30 text-30 text-fore-title font-bold">{{ prevData.name }}的刷题详情</view>
-      <practice-table :student-id="studentId" />
+      <practice-table :record-id="prevData.recordId" />
     </view>
   </ie-page>
 </template>
@@ -12,8 +12,8 @@
 import practiceTable from '@/pagesStudy/components/practice-table.vue';
 import { useTransferPage } from '@/hooks/useTransferPage';
 import { useScroll } from '@/hooks/useScroll';
+
 const { prevData } = useTransferPage();
-const studentId = ref(prevData.value.studentId);
 const { scrollTop } = useScroll();
 
 const pageTitle = computed(() => {

+ 20 - 2
src/pagesStudy/pages/study-video-detail/study-video-detail.vue

@@ -4,7 +4,7 @@
     <view class="h-20"></view>
     <view class="bg-white flex-1 min-h-1 relative">
       <view class="pt-30 px-32 text-30 text-fore-title font-bold">{{ prevData.name }}的视频观看统计</view>
-      <video-table :student-id="studentId" />
+      <video-table :data="data" />
     </view>
   </ie-page>
 </template>
@@ -12,9 +12,12 @@
 import videoTable from '@/pagesStudy/components/video-table.vue';
 import { useTransferPage } from '@/hooks/useTransferPage';
 import { useScroll } from '@/hooks/useScroll';
+import { Study } from '@/types';
+import { getStudentVideoStudyRecord } from '@/api/modules/study';
+
 const { prevData } = useTransferPage();
-const studentId = ref(prevData.value.studentId);
 const { scrollTop } = useScroll();
+const data = ref<Study.VideoStudy>({} as Study.VideoStudy);
 
 const pageTitle = computed(() => {
   if (scrollTop.value > 45) {
@@ -22,5 +25,20 @@ const pageTitle = computed(() => {
   }
   return '学生刷题详情';
 });
+
+const loadData = async () => {
+  uni.$ie.showLoading();
+  try {
+    const res = await getStudentVideoStudyRecord({
+      recordId: prevData.value.recordId,
+    });
+    data.value = res.data;
+  } finally {
+    uni.$ie.hideLoading();
+  }
+}
+onLoad(() => {
+  loadData();
+});
 </script>
 <style lang="scss" scoped></style>

+ 2 - 2
src/pagesSystem/pages/edit-profile/edit-profile.vue

@@ -52,7 +52,7 @@
             <ie-image v-if="form.examType === EnumExamType.OHS" slot="right" src="/static/image/icon-lock.png"
               custom-class="w-24 h-30" mode="aspectFill" />
           </uv-form-item>
-          <uv-form-item label="外语" prop="name" :borderBottom="form.examType === EnumExamType.OHS">
+          <uv-form-item label="外语" prop="name" :borderBottom="form.examType === EnumExamType.OHS || form.examType === EnumExamType.SVS">
             <uv-input v-model="scores.foreign" border="none"
               :placeholder="form.examType === EnumExamType.OHS ? '' : '请输入'" placeholderClass="text-30"
               font-size="30rpx" :custom-style="customStyle" :readonly="form.examType === EnumExamType.OHS">
@@ -60,7 +60,7 @@
             <ie-image v-if="form.examType === EnumExamType.OHS" slot="right" src="/static/image/icon-lock.png"
               custom-class="w-24 h-30" mode="aspectFill" />
           </uv-form-item>
-          <block v-if="[EnumExamType.OHS].includes(form.examType)">
+          <block v-if="[EnumExamType.OHS, EnumExamType.SVS].includes(form.examType)">
             <uv-form-item label="物理" prop="name" borderBottom>
               <uv-input v-model="scores.physics" border="none"
                 :placeholder="form.examType === EnumExamType.OHS ? '' : '请输入'" placeholderClass="text-30"

+ 3 - 1
src/pagesSystem/pages/login/login.vue

@@ -268,7 +268,9 @@ const handleValid = (data: { code: string; uuid: string }) => {
 onLoad(() => {
   rememberPassword.value[0] = userStore.rememberPwd;
   if (import.meta.env.DEV) {
-    agreePrivacy.value = [true]
+    agreePrivacy.value = [true];
+    phone.value = '17363958504';
+    password.value = '1234';
   }
   if (userStore.rememberPwd) {
     cardNo.value = userStore.cardNo;

+ 81 - 16
src/types/study.ts

@@ -16,26 +16,83 @@ export interface StudentStat {
   rate: number;
 }
 
-export interface StudentExamRecord {
+/**
+ * 班级知识点记录
+ */
+  export interface ClassKnowledgeRecord {
+  rate: number;
+  list: StudentPlanStudyRecord[];
+}
+
+/**
+ * 班级学生刷题记录
+ */
+export interface StudentPlanStudyRecord {
   id: number;
+  avatar?: string;
   name: string;
-  score: number;
-  status: number;
+  rate: number;
+  time: number;
+  total: number;
+  value: number;
 }
 
-export interface StudentVideoRecord {
+export interface StudentPracticeRecord {
   id: number;
+  avatar?: string;
   name: string;
-  date: string;
-  duration: number;
+  rate: number;
+  time: number;
+  total: number;
+  value: number;
 }
 
-export interface StudentVideoStat {
+export interface StudentExamRecord {
   id: number;
-  avatar?: string;
   name: string;
-  videoCount: number;
-  duration: number;
+  total: number;
+  value: number;
+  rate: number;
+}
+
+/**
+ * 组卷批次信息
+ */
+export interface Batch {
+  batchId: number;
+  name: string;
+  year: number;
+}
+
+/**
+ * 班级学生视频学习记录
+ */
+export interface StudentVideoRecord {
+  id: number;
+  name: string;
+  total: string;
+  value: number;
+  seq: number;
+}
+
+export interface PaperWorkRecord {
+  buildStatus: number | null;
+  count: number | null;
+}
+
+export interface PaperWorkRecordQuery {
+  buildStatus: number | null;
+  batchId: number | null;
+  buildType: number | null;
+  classId: number | null;
+  subjectId: number | null;
+}
+
+export interface PaperWorkRecordDetail {
+  className: string;
+  nickName: string;
+  state: string;
+  studentId: number;
 }
 
 /**
@@ -328,14 +385,22 @@ export interface SimulatedRecord {
   state: EnumSimulatedRecordStatus;
 }
 
-export interface VideoStudyRecord {
+/**
+ * 视频学习统计
+ */
+export interface VideoStudy {
   study: number;
   total: number;
-  list: {
-    name: string;
-    date: string;
-    study: string;
-  }[]
+  list: VideoStudyRecord[]
+}
+
+/**
+ * 视频学习记录
+ */
+export interface VideoStudyRecord {
+  name: string;
+  date: string;
+  study: string;
 }
 
 export interface PracticeRecord {