mo 1 year ago
parent
commit
26646495ff

+ 12 - 3
src/components/CBreadcrumb/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, ref } from 'vue';
+import { defineComponent, ref, watch } from 'vue';
 import styles from './index.module.less';
 import {
   NIcon,
@@ -27,6 +27,15 @@ export default defineComponent({
     const router = useRouter();
     const route = useRoute();
     const lastNum = ref(props.list?.length || 0);
+    const list = ref(props.list as any);
+    watch(
+      () => props.list,
+      (value: any) => {
+        console.log('list', value);
+        list.value = value;
+      },
+      { deep: true, immediate: true }
+    );
     return () => (
       <>
         <div class={styles.CBreadcrumb}>
@@ -37,8 +46,8 @@ export default defineComponent({
               onClick={() => router.go(-1)}
             />
             <NBreadcrumb separator="">
-              {props.list &&
-                props.list.map((item: any, index: number) => (
+              {list.value &&
+                list.value.map((item: any, index: number) => (
                   <>
                     <NBreadcrumbItem
                       onClick={() =>

+ 1 - 1
src/components/layout/layoutSilder.tsx

@@ -39,7 +39,7 @@ export default defineComponent({
         isActive: false,
         id: 3,
         path: '/classList',
-        lightList: ['/classDetail']
+        lightList: ['/classDetail', '/classStudentRecode', '/afterWorkDetail']
       },
       {
         activeIcon: studentIcon,

+ 11 - 0
src/router/routes/index.ts

@@ -66,6 +66,17 @@ export const constantRoutes: RouteRecordRaw[] = [
           singleLayout: 'blank'
         }
       },
+      {
+        path: '/classStudentRecode',
+        name: 'classStudentRecode',
+        component: () =>
+          import('@/views/classList/components/classStudentRecode'),
+        meta: {
+          title: '练习记录',
+          singleLayout: 'blank'
+        }
+      },
+
       //
 
       {

+ 9 - 0
src/utils/contants.ts

@@ -38,3 +38,12 @@ export const evaluateDifficult = {
   ADVANCED: '进阶级',
   PERFORMER: '大师级'
 } as any;
+
+/**
+ * 训练完成状态
+ */
+export const trainingStatus = {
+  UNSUBMITTED: '未提交',
+  SUBMITTED: '提交',
+  TARGET: '合格'
+} as any;

+ 16 - 0
src/utils/dateFormat.ts

@@ -78,3 +78,19 @@ export const getTimes = (
   }
   return {};
 };
+
+export function formatTime(time: number) {
+  const hours = Math.floor(time / 3600);
+
+  const minutes = Math.floor(Math.floor(time % 3600) / 60);
+
+  const seconds = Math.floor(time % 60);
+
+  const h = hours.toString().length === 1 ? `0${hours}` : hours;
+
+  const m = minutes.toString().length === 1 ? `0${minutes}` : minutes;
+
+  const s = seconds.toString().length === 1 ? `0${seconds}` : seconds;
+
+  return `${h} 小时 ${m} 分钟 ${s} 秒`;
+}

+ 3 - 0
src/utils/searchArray.ts

@@ -19,3 +19,6 @@ export const instrumentArray = getValueForKey(constant.instrument);
 
 // 资源类型
 export const resourceTypeArray = getValueForKey(constant.resourceType);
+
+// 训练状态
+export const trainingStatusArray = getValueForKey(constant.trainingStatus);

+ 39 - 0
src/views/classList/api.ts

@@ -66,3 +66,42 @@ export const getTrainingList = (params: any) => {
     data: params
   });
 };
+
+/**
+ * 获取作业信息
+ */
+export const getWorkDetail = (params: any) => {
+  return request.get('/edu-app/lessonTraining/trainingDetail', {
+    data: params,
+    params
+  });
+};
+
+/***
+ * 作业详情 列表
+ */
+export const getTrainingStudentList = (params: any) => {
+  return request.post('/edu-app/lessonTraining/trainingStudentList', {
+    data: params
+  });
+};
+
+/**
+ * 练习记录
+ */
+export const getTestList = (params: any) => {
+  return request.post('/edu-app/musicPracticeRecordStat/trainingRanking', {
+    data: params
+  });
+};
+
+/**
+ * 获取学生详情
+ */
+export const getStudentDetail = (params: any) => {
+  return request.get('/edu-app/student/detail', {
+    data: params,
+    params,
+    requestType: 'form'
+  });
+};

+ 4 - 1
src/views/classList/classDetail.tsx

@@ -5,6 +5,7 @@ import { useRoute, useRouter } from 'vue-router';
 import CBreadcrumb from '@/components/CBreadcrumb';
 import ClassStudent from './components/classStudent';
 import AfterWork from './components/afterWork';
+import TestRecode from './components/testRecode';
 export default defineComponent({
   name: 'base-setting',
   setup(props, { emit, attrs }) {
@@ -32,7 +33,9 @@ export default defineComponent({
             <NTabPane name="afterWork" tab="课后训练">
               <AfterWork></AfterWork>
             </NTabPane>
-            <NTabPane name="practice" tab="练习记录"></NTabPane>
+            <NTabPane name="practice" tab="练习记录">
+              <TestRecode></TestRecode>
+            </NTabPane>
             <NTabPane name="attendclass" tab="上课记录"></NTabPane>
           </NTabs>
         </div>

+ 97 - 30
src/views/classList/components/afterWorkDetail.tsx

@@ -12,23 +12,42 @@ import {
 import SearchInput from '@/components/searchInput';
 import CSelect from '@/components/CSelect';
 import Pagination from '@/components/pagination';
-import { getStudentList } from '../api';
+import { getWorkDetail, getTrainingStudentList } from '../api';
 import add from './images/add.png';
 import { useRoute } from 'vue-router';
 import CBreadcrumb from '/src/components/CBreadcrumb';
+import CDatePicker from '/src/components/CDatePicker';
+import defultHeade from '@/components/layout/images/teacherIcon.png';
+import {
+  getNowDateAndMonday,
+  getNowDateAndSunday,
+  getTimes
+} from '/src/utils/dateFormat';
+import { trainingStatusArray } from '@/utils/searchArray';
+import dayjs from 'dayjs';
 export default defineComponent({
   name: 'student-studentList',
   setup(props, { emit }) {
     const state = reactive({
-      searchForm: { keyword: '', status: null as any },
+      searchForm: { keyword: '', trainingStatus: null as any },
       loading: false,
       pagination: {
         page: 1,
         rows: 10,
         pageTotal: 4
       },
-      tableList: [] as any
+      tableList: [] as any,
+      workInfo: {
+        createTime: '',
+        expireDate: '',
+        teacherAvatar: '',
+        teacherName: ''
+      }
     });
+    const timer = ref<[number, number]>([
+      getNowDateAndMonday(new Date().getTime()),
+      getNowDateAndSunday(new Date().getTime())
+    ]);
     const route = useRoute();
     const routerList = ref([
       { name: '班级管理', path: '/classList' },
@@ -43,16 +62,21 @@ export default defineComponent({
     };
 
     const onReset = () => {
-      state.searchForm = { keyword: '', status: null as any };
+      state.searchForm = { keyword: '', trainingStatus: null as any };
+      timer.value = [
+        getNowDateAndMonday(new Date().getTime()),
+        getNowDateAndSunday(new Date().getTime())
+      ];
       search();
     };
     const getList = async () => {
       state.loading = true;
       try {
-        const res = await getStudentList({
-          classGroupId: route.query.id,
+        const res = await getTrainingStudentList({
+          trainingId: route.query.trainingId,
           ...state.searchForm,
-          ...state.pagination
+          ...state.pagination,
+          ...getTimes(timer.value, ['startTime', 'endTime'], 'YYYY-MM-DD')
         });
 
         state.tableList = res.data.rows;
@@ -64,34 +88,53 @@ export default defineComponent({
         console.log(e);
       }
     };
+    const getWorkInfo = async () => {
+      console.log(route.query);
+      try {
+        const res = await getWorkDetail({ trainingId: route.query.trainingId });
+        state.workInfo = { ...res.data };
+      } catch (e) {
+        console.log(e);
+      }
+    };
     onMounted(() => {
+      getWorkInfo();
       getList();
     });
     const columns = () => {
       return [
         {
           title: '学生姓名',
-          key: 'nickname'
+          key: 'studentName'
         },
         {
-          title: '手机号',
-          key: 'phone'
+          title: '最后提交时间',
+          key: 'submitTime',
+          render(row: any) {
+            return row.submitTime
+              ? dayjs(row.submitTime).format('YYYY-MM-DD')
+              : '--';
+          }
         },
         {
-          title: '性别',
+          title: '训练状态',
           key: 'sex',
           render(row: any) {
-            return <>{row.sex == '0' ? '女' : '男'}</>;
+            return (
+              <div>
+                {row.trainingStatus == 'UNSUBMITTED' ? (
+                  <p class={styles.nosub}>未提交</p>
+                ) : null}
+                {row.trainingStatus == 'SUBMITTED' ? (
+                  <p class={styles.ison}>不合格</p>
+                ) : null}
+                {row.trainingStatus == 'TARGET' ? (
+                  <p class={styles.isok}>合格</p>
+                ) : null}
+              </div>
+            );
           }
         },
-
-        // {
-        //   title: '学生类型',
-        //   key: 'studentType',
-        //   render(row: any) {
-        //     return <>{row.studentType == 'member' ? '会员' : '普通'}</>;
-        //   }
-        // },
         {
           title: '操作',
           key: 'id',
@@ -109,7 +152,31 @@ export default defineComponent({
       <div>
         <CBreadcrumb list={routerList.value}></CBreadcrumb>
         <div class={styles.listWrap}>
-          <div class={styles.teacherList}>{/* <div class={}> </div> */}</div>
+          <div class={styles.teacherList}>
+            <div class={styles.teacherHeader}>
+              <div class={styles.teacherHeaderBorder}>
+                <NImage
+                  class={styles.teacherHeaderImg}
+                  src={
+                    state.workInfo.teacherAvatar
+                      ? state.workInfo.teacherAvatar
+                      : defultHeade
+                  }
+                  previewDisabled></NImage>
+              </div>
+            </div>
+            <div class={styles.workafterInfo}>
+              <h4>{state.workInfo.teacherName}</h4>
+              <p>
+                布置时间:
+                {dayjs(state.workInfo.createTime).format('YYYY-MM-DD')} |{' '}
+                <span>
+                  截止时间:
+                  {dayjs(state.workInfo.expireDate).format('YYYY-MM-DD')}
+                </span>
+              </p>
+            </div>
+          </div>
           <div class={styles.searchList}>
             <NForm label-placement="left" inline>
               <NFormItem>
@@ -130,20 +197,20 @@ export default defineComponent({
                         label: '训练状态',
                         value: null
                       },
-                      {
-                        label: '已结束',
-                        value: 1
-                      },
-                      {
-                        label: '进行中',
-                        value: 0
-                      }
+                      ...trainingStatusArray
                     ],
                     placeholder: '训练状态',
                     clearable: true,
                     inline: true
                   } as any)}
-                  v-model:value={state.searchForm.status}></CSelect>
+                  v-model:value={state.searchForm.trainingStatus}></CSelect>
+              </NFormItem>
+              <NFormItem>
+                <CDatePicker
+                  v-model:value={timer.value}
+                  separator={'至'}
+                  type="daterange"
+                  timerValue={timer.value}></CDatePicker>
               </NFormItem>
 
               <NFormItem>

+ 100 - 0
src/views/classList/components/classStudentRecode.tsx

@@ -0,0 +1,100 @@
+import { defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from '../index.module.less';
+import {
+  NButton,
+  NDataTable,
+  NForm,
+  NFormItem,
+  NImage,
+  NSelect,
+  NSpace,
+  NTabPane,
+  NTabs
+} from 'naive-ui';
+import { getStudentDetail, getTrainingStudentList } from '../api';
+import { useRoute } from 'vue-router';
+import CBreadcrumb from '/src/components/CBreadcrumb';
+import defultHeade from '@/components/layout/images/teacherIcon.png';
+import femaleIcon from '@/views/setting/images/femaleIcon.png';
+import maleIcon from '@/views/setting/images/maleIcon.png';
+import PracticeData from '@/views/studentList/components/practiceData';
+import dayjs from 'dayjs';
+export default defineComponent({
+  name: 'classStudentRecode',
+  setup(props, { emit }) {
+    const state = reactive({
+      studentInfo: { avatar: '', nickname: '', gender: null, subjectNames: '' }
+    });
+    const activeStudentTab = ref('textRcode');
+    const route = useRoute();
+    const routerList = ref([
+      { name: '班级管理', path: '/classList' },
+      { name: route.query.name, path: '/classDetail' },
+      { name: route.query.studentName, path: '/classStudentRecode' }
+    ] as any);
+
+    const getWorkInfo = async () => {
+      console.log(route.query.studentId);
+      try {
+        const res = await getStudentDetail({
+          id: route.query.studentId
+        });
+        state.studentInfo = { ...res.data };
+      } catch (e) {
+        console.log(e);
+      }
+    };
+    onMounted(() => {
+      getWorkInfo();
+    });
+
+    return () => (
+      <div>
+        <CBreadcrumb list={routerList.value}></CBreadcrumb>
+        <div class={styles.listWrap}>
+          <div class={styles.teacherList}>
+            <div class={styles.teacherHeader}>
+              <div class={styles.teacherHeaderBorder}>
+                <NImage
+                  class={styles.teacherHeaderImg}
+                  src={
+                    state.studentInfo.avatar
+                      ? state.studentInfo.avatar
+                      : defultHeade
+                  }
+                  previewDisabled></NImage>
+              </div>
+            </div>
+            <div class={styles.workafterInfo}>
+              <h4 class={styles.studentGender}>
+                {state.studentInfo.nickname}{' '}
+                <NImage
+                  src={
+                    state.studentInfo.gender ? maleIcon : femaleIcon
+                  }></NImage>
+              </h4>
+              <p>
+                {route.query.name}{' '}
+                {state.studentInfo.subjectNames
+                  ? '|' + state.studentInfo.subjectNames
+                  : ''}
+              </p>
+            </div>
+          </div>
+          <NTabs
+            class={styles.customTabs}
+            v-model:value={activeStudentTab.value}
+            size="large"
+            animated
+            pane-wrapper-style="margin: 0 -4px"
+            pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;">
+            <NTabPane name="textRcode" tab="练习记录">
+              <PracticeData></PracticeData>
+            </NTabPane>
+            <NTabPane name="evaluatingRcode" tab="评测记录"></NTabPane>
+          </NTabs>
+        </div>
+      </div>
+    );
+  }
+});

+ 284 - 0
src/views/classList/components/testRecode.tsx

@@ -0,0 +1,284 @@
+import { defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from '../index.module.less';
+import {
+  NButton,
+  NDataTable,
+  NForm,
+  NFormItem,
+  NGi,
+  NGrid,
+  NImage,
+  NNumberAnimation,
+  NSelect,
+  NSpace
+} from 'naive-ui';
+import SearchInput from '@/components/searchInput';
+import CSelect from '@/components/CSelect';
+import Pagination from '@/components/pagination';
+import add from './images/add.png';
+import {
+  getNowDateAndMonday,
+  getNowDateAndSunday,
+  getTimes,
+  formatTime
+} from '/src/utils/dateFormat';
+import { getTestList } from '../api';
+import CDatePicker from '/src/components/CDatePicker';
+import { useRoute, useRouter } from 'vue-router';
+import { get } from 'lodash';
+export default defineComponent({
+  name: 'student-studentList',
+  setup(props, { emit }) {
+    const state = reactive({
+      searchForm: { keyword: '', trainingStatus: null as any, vipFlag: null },
+      searchWord: '',
+      orchestraType: null,
+      courseTypeCode: null,
+      subjectId: null,
+      classId: null,
+      studentType: null,
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 4
+      },
+      tableList: [{ studentId: '1000578', studentName: '一十四' }] as any,
+      memberNumber: 0,
+      testInfo: {
+        practiceDurationAvg: 0,
+        memberCount: 0
+      }
+    });
+    const route = useRoute();
+    const router = useRouter();
+    const search = () => {
+      state.pagination.page = 1;
+      getList();
+    };
+    const timer = ref<[number, number]>([
+      getNowDateAndMonday(new Date().getTime()),
+      getNowDateAndSunday(new Date().getTime())
+    ]);
+    const onReset = () => {
+      state.searchForm = {
+        keyword: '',
+        trainingStatus: null as any,
+        vipFlag: null
+      };
+      search();
+    };
+    const getList = async () => {
+      state.loading = true;
+      try {
+        const res = await getTestList({
+          classGroupId: route.query.id,
+          ...state.searchForm,
+          ...state.pagination,
+          ...getTimes(timer.value, ['startTime', 'endTime'], 'YYYY-MM-DD')
+        });
+
+        // state.tableList = res.data.rows;
+
+        state.pagination.pageTotal = res.data.total;
+        state.loading = false;
+      } catch (e) {
+        state.loading = false;
+        console.log(e);
+      }
+    };
+    onMounted(() => {
+      getList();
+    });
+    const gotoStudentDetail = (row: any) => {
+      router.push({
+        path: '/classStudentRecode',
+        query: {
+          ...route.query,
+          studentId: row.studentId,
+          studentName: row.studentName
+        }
+      });
+    };
+    const columns = () => {
+      return [
+        {
+          title: '姓名',
+          key: 'studentName'
+        },
+        {
+          title: '手机号',
+          key: 'studentPhone'
+        },
+        {
+          title: '性别',
+          key: 'sex',
+          render(row: any) {
+            return <>{row.sex == '0' ? '女' : '男'}</>;
+          }
+        },
+        {
+          title: '学生类型',
+          key: 'studentType',
+          render(row: any) {
+            return <>{row.studentType == 'member' ? '会员' : '普通'}</>;
+          }
+        },
+        {
+          title: '练习天数',
+          key: 'practiceDays',
+          render(row: any) {
+            return <>{row.practiceDays ? row.practiceDays : 0}天</>;
+          }
+        },
+        {
+          title: '练习时长',
+          key: 'studentType',
+          render(row: any) {
+            return (
+              <>{row.practiceDuration ? formatTime(row.practiceDuration) : 0}</>
+            );
+          }
+        },
+        {
+          title: '操作',
+          key: 'id',
+          render(row: any) {
+            return (
+              <NButton
+                text
+                type="primary"
+                onClick={() => {
+                  gotoStudentDetail(row);
+                }}>
+                详情
+              </NButton>
+            );
+          }
+        }
+      ];
+    };
+    return () => (
+      <div>
+        <div class={styles.searchList}>
+          <NForm label-placement="left" inline>
+            <NFormItem>
+              <SearchInput
+                {...{ placeholder: '请输入学生姓名' }}
+                class={styles.searchInput}
+                searchWord={state.searchForm.keyword}
+                onChangeValue={(val: string) =>
+                  (state.searchForm.keyword = val)
+                }></SearchInput>
+            </NFormItem>
+
+            <NFormItem>
+              <CSelect
+                {...({
+                  options: [
+                    {
+                      label: '学生类型',
+                      value: null
+                    },
+                    {
+                      label: '会员',
+                      value: true
+                    },
+                    {
+                      label: '普通',
+                      value: false
+                    }
+                  ],
+                  placeholder: '学生类型',
+                  clearable: true,
+                  inline: true
+                } as any)}
+                v-model:value={state.searchForm.vipFlag}></CSelect>
+            </NFormItem>
+            <NFormItem>
+              <CDatePicker
+                v-model:value={timer.value}
+                separator={'至'}
+                type="daterange"
+                timerValue={timer.value}></CDatePicker>
+            </NFormItem>
+            <NFormItem>
+              <NSpace justify="end">
+                <NButton type="primary" class="searchBtn" onClick={search}>
+                  搜索
+                </NButton>
+                <NButton
+                  type="primary"
+                  ghost
+                  class="resetBtn"
+                  onClick={onReset}>
+                  重置
+                </NButton>
+              </NSpace>
+            </NFormItem>
+          </NForm>
+        </div>
+        <div class={['section-container']}>
+          <NGrid x-gap="12" cols={8}>
+            <NGi>
+              <div class={styles.TrainDataItem}>
+                <p class={styles.TrainDataItemTitle}>
+                  <span>
+                    <NNumberAnimation
+                      from={0}
+                      to={state.pagination.pageTotal}></NNumberAnimation>
+                  </span>
+                  人
+                </p>
+                <p class={styles.TrainDataItemsubTitle}>练习人数</p>
+              </div>
+            </NGi>
+            <NGi>
+              <div class={styles.TrainDataItem}>
+                <p class={styles.TrainDataItemTitle}>
+                  <span>
+                    <NNumberAnimation
+                      from={0}
+                      to={state.testInfo.memberCount}></NNumberAnimation>
+                  </span>
+                  人
+                </p>
+                <p class={styles.TrainDataItemsubTitle}>会员人数</p>
+              </div>
+            </NGi>
+            <NGi>
+              <div class={styles.TrainDataItem}>
+                <p class={styles.TrainDataItemTitle}>
+                  <span>
+                    <NNumberAnimation
+                      from={0}
+                      to={
+                        state.testInfo.practiceDurationAvg
+                      }></NNumberAnimation>
+                  </span>
+                  分钟
+                </p>
+                <p class={styles.TrainDataItemsubTitle}>平均练习时长</p>
+              </div>
+            </NGi>
+          </NGrid>
+        </div>
+        <div class={styles.tableWrap}>
+          <NDataTable
+            class={styles.classTable}
+            loading={state.loading}
+            columns={columns()}
+            data={state.tableList}></NDataTable>
+          <Pagination
+            v-model:page={state.pagination.page}
+            v-model:pageSize={state.pagination.rows}
+            v-model:pageTotal={state.pagination.pageTotal}
+            onList={getList}
+            sync
+            saveKey="orchestraRegistration-key"
+          />
+        </div>
+      </div>
+    );
+  }
+});

+ 107 - 0
src/views/classList/index.module.less

@@ -363,3 +363,110 @@
     }
   }
 }
+.teacherList {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-bottom: 32px;
+  .teacherHeader {
+    width: 100px;
+    height: 100px;
+    padding: 4px;
+    border-radius: 99px;
+    background: linear-gradient(
+      228deg,
+      rgba(2, 186, 255, 1),
+      rgba(0, 122, 254, 1)
+    );
+    margin-right: 20px;
+    .teacherHeaderBorder {
+      width: 100%;
+      height: 100%;
+      background: #fff;
+      border-radius: 99px;
+      overflow: hidden;
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      justify-content: center;
+      padding: 4px;
+    }
+  }
+
+  .teacherHeaderImg {
+    width: 84px;
+    height: 84px;
+    border-radius: 50%;
+    overflow: hidden;
+  }
+  .workafterInfo {
+    h4 {
+      font-size: 22px;
+      line-height: 30px;
+      font-weight: 600;
+      color: #131415;
+      margin-bottom: 12px;
+    }
+    p {
+      font-size: 16px;
+      line-height: 22px;
+      color: #777;
+      span {
+        color: #ea4132;
+      }
+    }
+  }
+}
+.isok {
+  font-weight: 600;
+  color: #333333;
+}
+.ison {
+  font-weight: 600;
+  color: #ea4132;
+}
+.nosub {
+  font-weight: 600;
+  color: #aaa;
+}
+
+.TrainDataItem {
+  margin-bottom: 20px;
+
+  .TrainDataItemTitle {
+    text-align: left;
+    font-size: 13px;
+    font-weight: 400;
+    color: #777777;
+    line-height: 18px;
+
+    span {
+      font-family: 'DINA';
+      font-size: 26px;
+      font-weight: 600;
+      color: #131415;
+      line-height: 28px;
+    }
+  }
+
+  .TrainDataItemsubTitle {
+    margin-top: 4px;
+    text-align: left;
+    font-size: 13px;
+    font-family: PingFangSC-Regular, PingFang SC;
+    font-weight: 400;
+    color: #777777;
+    line-height: 18px;
+  }
+}
+
+.studentGender {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  img {
+    margin-left: 11px;
+    width: 10px;
+    height: 20px;
+  }
+}

+ 286 - 0
src/views/studentList/components/practiceData.tsx

@@ -0,0 +1,286 @@
+import { Ref, defineComponent, reactive, ref } from 'vue';
+import styles from '../index.module.less';
+import { NButton, NDataTable, NNumberAnimation } from 'naive-ui';
+import numeral from 'numeral';
+import { useECharts } from '@/hooks/web/useECharts';
+import Pagination from '/src/components/pagination';
+export default defineComponent({
+  name: 'student-practiceData',
+  setup() {
+    const chartRef = ref<HTMLDivElement | null>(null);
+    const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+    const practiceFlag = ref(true);
+    const payForm = reactive({
+      height: '360px',
+      width: '100%',
+      studentNum: 0,
+      paymentAmount: 0,
+      dateList: [
+        '2022-10-10',
+        '2022-10-11',
+        '2022-10-12',
+        '2022-10-13',
+        '2022-10-14',
+        '2022-10-15',
+        '2022-10-16'
+      ],
+      timeList: [90, 128, 242, 120, 186, 77, 148]
+    });
+
+    const state = reactive({
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 4
+      },
+      tableList: [
+        {
+          teacherName: '孙忆枫',
+          createTime: '2023-06-27',
+          endTime: '2023-06-30',
+          status: 'ing',
+          studentNum: 100,
+          submitNum: 100,
+          quantityNum: 60,
+          submitRate: 100,
+          quantityRate: 60
+        },
+        {
+          teacherName: '孙忆枫',
+          createTime: '2023-06-27',
+          endTime: '2023-06-30',
+          status: 'ing',
+          studentNum: 100,
+          submitNum: 100,
+          quantityNum: 60,
+          submitRate: 100,
+          quantityRate: 60
+        },
+        {
+          teacherName: '孙忆枫',
+          createTime: '2023-06-27',
+          endTime: '2023-06-30',
+          status: 'ing',
+          studentNum: 100,
+          submitNum: 100,
+          quantityNum: 60,
+          submitRate: 100,
+          quantityRate: 60
+        },
+        {
+          teacherName: '孙忆枫',
+          createTime: '2023-06-25',
+          endTime: '2023-06-26',
+          status: 'end',
+          studentNum: 100,
+          submitNum: 100,
+          quantityNum: 60,
+          submitRate: 100,
+          quantityRate: 60
+        }
+      ] as any,
+      goCourseVisiable: false
+    });
+    const columns = () => {
+      return [
+        {
+          title: '日期',
+          key: 'createTime'
+        },
+        {
+          title: '练习人数',
+          key: 'quantityNum',
+          render(row: any) {
+            return <span>{row.quantityNum}人</span>;
+          }
+        },
+
+        {
+          title: '平均练习时长(分钟)',
+          key: 'submitNum',
+          render(row: any) {
+            return <span>{row.submitNum}分钟</span>;
+          }
+        }
+      ];
+    };
+    const getList = () => {
+      console.log('1');
+    };
+    const setChart = () => {
+      setOptions({
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            type: 'none'
+          }
+        },
+        legend: {
+          show: false,
+          selected: {
+            //在这里设置默认展示就ok了
+            '练习时长(分钟)': practiceFlag.value
+          }
+        },
+        xAxis: {
+          type: 'category',
+          boundaryGap: true,
+          axisLabel: {
+            show: true,
+            interval: 0
+          },
+          data: payForm.dateList
+        },
+        yAxis: [
+          {
+            type: 'value',
+            axisLabel: {
+              formatter: '{value} min'
+            },
+            axisTick: {
+              show: false
+            },
+            splitArea: {
+              show: false,
+              areaStyle: {
+                color: ['rgba(255,255,255,0.2)']
+              }
+            }
+          }
+        ],
+        grid: {
+          left: '1%',
+          right: '1%',
+          top: '2%',
+          bottom: 0,
+          containLabel: true
+        },
+        series: [
+          {
+            // smooth: true,
+            data: payForm.timeList,
+            type: 'bar',
+            barWidth: '48px',
+            label: {
+              // 柱图头部显示值
+              show: true,
+              position: 'top',
+              color: '#333',
+              fontSize: '12px',
+              fontWeight: 600
+            },
+
+            itemStyle: {
+              normal: {
+                //这里设置柱形图圆角 [左上角,右上角,右下角,左下角]
+                barBorderRadius: [8, 8, 0, 0],
+                color: '#D5E9FF'
+              },
+              emphasis: {
+                color: '#3583FA' //hover时改变柱子颜色
+                // borderWidth: 4,
+                // borderColor: 'rgba(213, 233, 255,.4)',
+                // borderType: 'solid'
+              }
+            } as any
+          }
+        ],
+
+        formatter: (item: any) => {
+          if (Array.isArray(item)) {
+            return [
+              item[0].axisValueLabel,
+              ...item.map(
+                (d: any) =>
+                  `<br/>${
+                    d.marker
+                  }<span style="margin-top:10px;margin-left:5px;font-size: 13px;font-weight: 500;
+                  color: #131415;font-weight: 600;
+                  margin-top:12px
+                  line-height: 18px;">练习时长: ${d.value}${'分钟'} </span>`
+              )
+            ].join('');
+          } else {
+            return item;
+          }
+        }
+        // dataZoom: [
+        //   {
+        //     type: 'slider',
+        //     start: 5,
+        //     end: 100,
+        //     filterMode: 'empty'
+        //   }
+        // ]
+      });
+    };
+    setChart();
+    return () => (
+      <>
+        <div class={styles.homeTrainData}>
+          <div class={styles.TrainDataTop}>
+            <div class={styles.TrainDataTopLeft}>
+              <div class={styles.TrainDataItem}>
+                <p class={styles.TrainDataItemTitle}>
+                  <span>
+                    <NNumberAnimation from={0} to={32}></NNumberAnimation>
+                  </span>
+                  人
+                </p>
+                <p class={styles.TrainDataItemsubTitle}>练习人数</p>
+              </div>
+              <div class={styles.TrainDataItem}>
+                <p class={styles.TrainDataItemTitle}>
+                  <span>
+                    <NNumberAnimation from={0} to={32}></NNumberAnimation>
+                  </span>
+                  分钟
+                </p>
+                <p class={styles.TrainDataItemsubTitle}>平均练习时长</p>
+              </div>
+            </div>
+            <div class={styles.TrainDataTopRight}>
+              <div
+                // onClick={() => {
+                //   practiceFlag.value = !practiceFlag.value;
+                //   setChart();
+                // }}
+                class={[
+                  styles.DataTopRightItem,
+                  practiceFlag.value ? '' : styles.DataTopRightItemDis
+                ]}>
+                <div
+                  class={[
+                    styles.DataTopRightDot,
+                    styles.DataTopRightDotBlue
+                  ]}></div>
+                <p>练习时长(分钟)</p>
+              </div>
+            </div>
+          </div>
+          <div class={styles.chatrs}>
+            <div
+              ref={chartRef}
+              style={{ height: payForm.height, width: payForm.width }}></div>
+          </div>
+          <div class={styles.tableWrap}>
+            <NDataTable
+              class={styles.classTable}
+              loading={state.loading}
+              columns={columns()}
+              data={state.tableList}></NDataTable>
+            <Pagination
+              v-model:page={state.pagination.page}
+              v-model:pageSize={state.pagination.rows}
+              v-model:pageTotal={state.pagination.pageTotal}
+              onList={getList}
+              sync
+              saveKey="orchestraRegistration-key"
+            />
+          </div>
+        </div>
+      </>
+    );
+  }
+});

+ 175 - 0
src/views/studentList/index.module.less

@@ -24,3 +24,178 @@
     }
   }
 }
+
+// 学情
+
+.homeStudy {
+  background-color: #fff;
+
+  .homeStudyTitle {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    font-size: 20px;
+
+    font-weight: 600;
+    color: #131415;
+    line-height: 28px;
+
+    .homeStudyTitleDot {
+      width: 5px;
+      height: 16px;
+      background: #198cfe;
+      margin-right: 8px;
+    }
+  }
+
+  .homeStudyInfoList {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: space-between;
+    margin-top: 21px;
+
+    .homeStudyInfoTabs {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+
+      .homeStudyInfoTabItem {
+        cursor: pointer;
+        width: 112px;
+        height: 39px;
+        border-radius: 20px;
+        font-size: 18px;
+        font-weight: 600;
+        line-height: 39px;
+        text-align: center;
+        margin-right: 24px;
+        background: #f5f6fa;
+        color: rgba(0, 0, 0, 0.5);
+
+        &:hover {
+          background: #198cfe;
+          color: #ffffff;
+          opacity: 0.8;
+        }
+      }
+
+      .homeStudyInfoTabItem.active {
+        background: #198cfe;
+        color: #ffffff;
+      }
+    }
+
+    .homeStudyInfoDate {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      justify-content: flex-end;
+    }
+  }
+
+  .searchBtn {
+    width: 90px;
+    height: 43px;
+    background: #198cfe;
+    border-radius: 8px;
+    line-height: 41px;
+    font-weight: 600 !important;
+    font-size: 18px;
+  }
+
+  .resetBtn {
+    width: 90px;
+    height: 43px;
+    border-radius: 8px;
+    line-height: 41px;
+    font-weight: 600 !important;
+    font-size: 18px;
+  }
+}
+
+.homeTrainData {
+  margin-top: 40px;
+
+  .TrainDataTop {
+    margin-bottom: 40px;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: space-between;
+
+    .TrainDataTopLeft {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+
+      .TrainDataItem {
+        margin-right: 40px;
+
+        .TrainDataItemTitle {
+          text-align: center;
+          font-size: 13px;
+          font-weight: 400;
+          color: #777777;
+          line-height: 18px;
+
+          span {
+            font-family: 'DINA';
+            font-size: 26px;
+            font-weight: 600;
+            color: #131415;
+            line-height: 28px;
+          }
+        }
+
+        .TrainDataItemsubTitle {
+          margin-top: 4px;
+          text-align: center;
+          font-size: 13px;
+          font-family: PingFangSC-Regular, PingFang SC;
+          font-weight: 400;
+          color: #777777;
+          line-height: 18px;
+        }
+      }
+    }
+
+    .TrainDataTopRight {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+
+      .DataTopRightItem {
+        cursor: pointer;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        margin-left: 30px;
+
+        &:hover {
+          opacity: 0.8;
+        }
+
+        .DataTopRightDot {
+          width: 16px;
+          height: 16px;
+          background: #3583fa;
+          border-radius: 4px;
+          margin-right: 6px;
+        }
+        .DataTopRightDot.DataTopRightDotBlue {
+          background: #d5e9ff;
+        }
+        .DataTopRightDot.red {
+          background: #ff7aa7;
+        }
+      }
+
+      .DataTopRightItem.DataTopRightItemDis {
+        .DataTopRightDot {
+          background: #f5f6fa;
+        }
+      }
+    }
+  }
+}