Browse Source

feat: :sparkles: 学校级看板-页面布局初始化

chaooo 1 year ago
parent
commit
1e28f7a957

+ 41 - 0
src/components/Charts/LineChart.vue

@@ -99,6 +99,11 @@ const options = {
       alignWithLabel: true,
     },
   },
+  legend: {
+    top: 0,
+    right: "5%",
+    data: ["", "水舞", "恐龙", "专注力训练"],
+  },
   series: [
     {
       data: props.dataSets?.[0] || [],
@@ -127,6 +132,42 @@ const options = {
         },
       },
     },
+    {
+      name: "水舞",
+      data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 30],
+      type: "line",
+      color: "#6f95d4",
+      symbolSize: 0,
+      lineStyle: {
+        normal: {
+          color: "#6f95d4",
+        },
+      },
+    },
+    {
+      name: "恐龙",
+      data: [1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20],
+      type: "line",
+      color: "#7bdb64",
+      symbolSize: 0,
+      lineStyle: {
+        normal: {
+          color: "#7bdb64",
+        },
+      },
+    },
+    {
+      name: "专注力训练",
+      data: [1, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16, 9, 10, 50],
+      type: "line",
+      color: "#db8364",
+      symbolSize: 0,
+      lineStyle: {
+        normal: {
+          color: "#db8364",
+        },
+      },
+    },
   ],
   visualMap: {
     type: "continuous",

+ 25 - 0
src/store/modules/permission.ts

@@ -51,8 +51,33 @@ const adminRoutes: RouteRecordRaw[] = JSON.parse(
             breadcrumb: false,
           },
         },
+        {
+          path: "training",
+          component: "admin/training/index",
+          meta: {title: "训练报告", name: "TrainingIndex", hidden: true},
+        },
+        {
+          path: "evaluation",
+          component: "admin/evaluation/index",
+          meta: {title: "测评数据对比", name: "EvaluationResult", hidden: true},
+        },
+        {
+          path: "report",
+          component: "customer/training/result",
+          meta: {title: "报告详情", name: "TrainingReport", hidden: true},
+        },
+        {
+          path: "result",
+          component: "customer/student/result",
+          meta: {title: "训练效果分析", name: "TrainingResult", hidden: true},
+        },
       ],
     },
+    {
+      path: "/download/student/result",
+      component: "customer/student/download",
+      meta: {title: "下载报告预览", name: "StudentDownload", hidden: true},
+    },
   ])
 );
 // 角色【学校负责人】拥有的权限路由

+ 2 - 2
src/views/admin/components/ComboCharts.vue

@@ -162,7 +162,7 @@ watchEffect(() => {
                     font-size="30px"
                     :round-cap="Boolean(true)" />
                   <p>初期训练</p>
-                  <p>专注力50以上人数比例</p>
+                  <p>专注力50以上训练占比</p>
                 </div>
               </el-col>
               <el-col :span="12">
@@ -179,7 +179,7 @@ watchEffect(() => {
                     font-size="30px"
                     :round-cap="Boolean(true)" />
                   <p>近期训练</p>
-                  <p>专注力50以上人数比例</p>
+                  <p>专注力50以上训练占比</p>
                 </div>
               </el-col>
             </el-row>

+ 271 - 0
src/views/admin/evaluation/index.vue

@@ -0,0 +1,271 @@
+<script setup lang="ts">
+import RadarChart from "@/components/Charts/RadarChart.vue";
+import FocusBarChart from "@/components/Charts/FocusBarChart.vue";
+import RelaxBarChart from "@/components/Charts/RelaxBarChart.vue";
+import IndicatorsBarChart from "@/components/Charts/IndicatorsBarChart.vue";
+import {ChartBtmData, ChartMidData} from "@/api/evaluation/types";
+import {getEvaluationBtm, getEvaluationMid} from "@/api/evaluation";
+
+defineOptions({
+  name: "EvaluateIndex",
+  inheritAttrs: false,
+});
+/**
+ * 图表数据
+ */
+const chartStatus = ref(false);
+//中间部分图表
+const midStatus = ref(false);
+const midData = ref<ChartMidData>();
+// 五维雷达图1
+const radarMidData = ref<number[][]>();
+const radarMidStar = ref<number[]>();
+// 专注力四维柱状图
+const focusData = ref<number[][]>();
+const focusStar = ref([1, 2, 3, 4]);
+async function getMidChartData(schoolId: number, babyId: number) {
+  midStatus.value = false;
+  getEvaluationMid(schoolId, babyId)
+    .then(({data}) => {
+      midData.value = data;
+      // 五维雷达图
+      radarMidData.value = [];
+      if (midData.value?.lastRadar[0] && midData.value?.lastRadar[0].length > 0) {
+        radarMidData.value?.push(midData.value?.lastRadar[0] || []);
+        radarMidStar.value = midData.value?.lastRadar[1] || [];
+      } else {
+        radarMidStar.value = midData.value?.firstRadar[1] || [];
+      }
+      radarMidData.value?.push(midData.value?.firstRadar[0] || []);
+      // 专注力四维柱状图
+      focusData.value = [];
+      focusData.value?.push(midData.value?.firstColumnar || []);
+      focusData.value?.push(midData.value?.lastColumnar || []);
+
+      midStatus.value = true;
+      chartStatus.value = true;
+    })
+    .catch((error) => {
+      midStatus.value = false;
+      console.log(error.message);
+    });
+}
+//底部图表、
+const btmStatus = ref(false);
+const btmData = ref<ChartBtmData>();
+// 五维雷达图2
+const radarBtmData = ref<number[][]>();
+const radarBtmStar = ref<number[]>();
+// 3维放松度分析柱状图
+const relaxData = ref<number[][]>();
+// 脑电评估检测指数分析看板
+const indicatorsData = ref<number[][]>();
+async function getBtmChartData(schoolId: number, babyId: number) {
+  btmStatus.value = false;
+  getEvaluationBtm(schoolId, babyId)
+    .then(({data}) => {
+      btmData.value = data;
+      // 五维雷达图2
+      radarBtmData.value = [];
+      if (btmData.value?.lastRadar[0] && btmData.value?.lastRadar[0].length > 0) {
+        radarBtmData.value?.push(btmData.value?.lastRadar[0] || []);
+        radarBtmStar.value = btmData.value?.lastRadar[1] || [];
+      } else {
+        radarBtmStar.value = btmData.value?.firstRadar[1] || [];
+      }
+      radarBtmData.value?.push(btmData.value?.firstRadar[0] || []);
+      // 3维放松度分析柱状图
+      relaxData.value = [];
+      relaxData.value?.push(btmData.value?.firstColumn || []);
+      relaxData.value?.push(btmData.value?.lastColumn || []);
+      // 脑电评估检测指数分析看板
+      indicatorsData.value = [];
+      indicatorsData.value?.push(btmData.value?.secondColumn || []);
+      indicatorsData.value?.push(btmData.value?.lastSecondColumn || []);
+      btmStatus.value = true;
+      chartStatus.value = true;
+    })
+    .catch((error) => {
+      btmStatus.value = false;
+      console.log(error.message);
+    });
+}
+
+onMounted(() => {
+  // 图表数据
+  getMidChartData(95, 832);
+  getBtmChartData(95, 832);
+});
+</script>
+
+<template>
+  <div class="evaluate-container">
+    <!-- 图表展示-->
+    <div v-if="chartStatus" class="student-chart">
+      <template v-if="midStatus">
+        <div class="title">儿童脑电专注力测评数据分析</div>
+        <el-row :gutter="10">
+          <el-col :xs="24" :span="8">
+            <div class="box-card">
+              <RadarChart
+                id="radarChart1"
+                :key="radarMidData?.toString()"
+                :data-sets="radarMidData"
+                :star="radarMidStar"
+                :tag="Boolean(true)"
+                width="400px"
+                height="300px"
+                class="chart" />
+              <div class="info">5D脑电数据模型</div>
+            </div>
+          </el-col>
+          <el-col :xs="24" :span="8">
+            <div class="box-card">
+              <FocusBarChart
+                id="focusBarChart1"
+                :key="focusData?.toString()"
+                :data-sets="focusData"
+                :star="focusStar"
+                width="400px"
+                height="300px"
+                class="chart" />
+              <div class="info legend">
+                专注力四维分析
+                <div class="tag">
+                  <span class="t1">首次检测</span>
+                  <span class="t2">最近检测</span>
+                </div>
+              </div>
+            </div>
+          </el-col>
+          <el-col :xs="24" :span="8" />
+        </el-row>
+      </template>
+      <template v-if="btmStatus">
+        <div class="title">脑电检测分析</div>
+        <el-row :gutter="10">
+          <el-col :xs="24" :span="8">
+            <div class="box-card">
+              <RadarChart
+                id="radarChart2"
+                :key="radarBtmData?.toString()"
+                :data-sets="radarBtmData"
+                :star="radarBtmStar"
+                :tag="Boolean(true)"
+                width="400px"
+                height="300px"
+                class="chart" />
+              <div class="info">5D脑电数据模型</div>
+            </div>
+          </el-col>
+          <el-col :xs="24" :span="8">
+            <div class="box-card">
+              <RelaxBarChart
+                id="relaxBarChart1"
+                :key="relaxData?.toString()"
+                :data-sets="relaxData"
+                class="chart"
+                height="300px"
+                width="400px" />
+              <div class="info legend">
+                3维放松度分析
+                <div class="tag">
+                  <span class="t1">首次检测</span>
+                  <span class="t2">最近检测</span>
+                </div>
+              </div>
+            </div>
+          </el-col>
+          <el-col :xs="24" :span="8">
+            <div class="box-card">
+              <IndicatorsBarChart
+                id="indicatorsBarChart1"
+                :key="indicatorsData?.toString()"
+                :data-sets="indicatorsData"
+                width="400px"
+                height="300px"
+                class="chart" />
+              <div class="info legend">
+                脑电评估检测指数分析看板
+                <div class="tag">
+                  <span class="t1">首次检测</span>
+                  <span class="t2">最近检测</span>
+                </div>
+              </div>
+            </div>
+          </el-col>
+        </el-row>
+      </template>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.evaluate-container {
+  position: relative;
+  padding: 30px;
+  background: #fff;
+}
+
+.empty {
+  padding: 135px 0;
+}
+
+.student-chart {
+  width: 1200px;
+
+  .title {
+    height: 54px;
+    font-size: 18px;
+    line-height: 54px;
+    color: #23283c;
+  }
+
+  .box-card {
+    box-sizing: border-box;
+    height: 338px;
+    overflow: hidden;
+    background: #f3f6fd;
+    border-radius: 20px;
+
+    .info {
+      height: 38px;
+      font-size: 14px;
+      line-height: 38px;
+      color: #fff;
+      text-align: center;
+      background: #4284f2;
+
+      &.legend {
+        position: relative;
+        box-sizing: border-box;
+        padding-right: 180px;
+
+        .tag {
+          position: absolute;
+          top: 0;
+          right: 20px;
+          white-space: nowrap;
+
+          .t1::before,
+          .t2::before {
+            display: inline-block;
+            width: 20px;
+            height: 10px;
+            margin: 0 4px 0 10px;
+            content: "";
+          }
+
+          .t1::before {
+            background: #f8b865;
+          }
+
+          .t2::before {
+            background: #91cc75;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 216 - 6
src/views/admin/school/index.vue

@@ -5,6 +5,11 @@ import {getGradeSelect} from "@/api/grade";
 import {getAreaSchool} from "@/api/areaboard";
 import {SchoolList} from "@/api/school/types";
 import ComboCharts from "@/views/admin/components/ComboCharts.vue";
+import {getUrlParam, trimInput} from "@/utils";
+import {StudentItem, StudentParams} from "@/api/student/types";
+import {getStudentLists} from "@/api/student";
+import {useRouter} from "vue-router";
+const router = useRouter();
 
 defineOptions({
   name: "DashboardSchool",
@@ -60,23 +65,97 @@ function changeDate() {
   dataParams.end_time = Math.ceil(datePicker.value[1] / 1000);
 }
 // 获取图表数据条件
+const chartStatus = ref(false);
 const chartParams: AreaChartParams = reactive({});
 /**
  * 获取页面数据
  */
-function getPageData() {
+function getSchoolPageData() {
+  // 图表参数
   chartParams.school_id = dataParams.school_id;
   chartParams.grade_id = dataParams.grade_id;
   chartParams.start_time = dataParams.start_time;
   chartParams.end_time = dataParams.end_time;
+  chartStatus.value = true;
+  // 学生参数
+  studentParams.school_id = dataParams.school_id;
+  studentParams.grade_id = dataParams.grade_id;
+  getStudentData();
+}
+/**
+ * 学生数据
+ */
+const studentParams: StudentParams = reactive({
+  school_id: 0,
+  grade_id: 0,
+  search: "",
+  page: 1,
+  page_size: 10,
+});
+const dataMessage = ref("加载中...");
+const studentCount = ref(0);
+const studentData = ref<StudentItem[]>();
+async function getStudentData() {
+  getStudentLists(studentParams)
+    .then(({data}) => {
+      const {count, lists} = data;
+      studentCount.value = count;
+      studentData.value = lists;
+      if (!(count && count > 0 && lists.length > 0)) {
+        dataMessage.value = "没有符合搜索条件的记录!";
+        if (studentParams.grade_id == 0 && studentParams.search == "") {
+          dataMessage.value = "暂时还没有任何学生绑定学校!";
+        }
+      }
+      //window.scroll({top: 2000, left: 0, behavior: "smooth"});
+    })
+    .catch((error) => {
+      dataMessage.value = error.message;
+      console.log(error.message);
+    });
+}
+function alertTrainingError() {
+  ElMessage.error("该学生训练数据还不足以进行训练效果分析,请至少完成16次专注力训练后再来查看。");
+}
+function alertEvaluationError() {
+  ElMessage.error("该学生还未进行过测评,暂无测评数据。");
+}
+function getSchoolSearch() {
+  const current = router.currentRoute.value.path;
+  router.push(
+    current +
+      "?school=" +
+      dataParams.school_id +
+      "&grade=" +
+      dataParams.grade_id +
+      "&start=" +
+      dataParams.start_time +
+      "&end=" +
+      dataParams.end_time
+  );
 }
-
 onMounted(() => {
   // 获取学校
   getSchoolData();
   gradeData.value = [{id: 0, name: "全部班级"}];
+  let school = getUrlParam("school");
+  if (school && Number(school) > 0) {
+    dataParams.school_id = Number(school);
+  }
+  let grade = getUrlParam("grade");
+  if (grade && Number(grade) > 0) {
+    dataParams.grade_id = Number(grade);
+  }
+  let start = getUrlParam("start");
+  if (start && Number(start) > 0) {
+    dataParams.start_time = Number(start);
+  }
+  let end = getUrlParam("end");
+  if (end && Number(end) > 0) {
+    dataParams.end_time = Number(end);
+  }
   // 获取页面数据
-  getPageData();
+  getSchoolPageData();
 });
 </script>
 
@@ -85,6 +164,7 @@ onMounted(() => {
     <div class="search-box s2">
       <el-select
         v-model="dataParams.school_id"
+        filterable
         placeholder="全部学校"
         size="large"
         @change="getGradeData(dataParams.school_id)">
@@ -104,15 +184,68 @@ onMounted(() => {
           value-format="x"
           @change="changeDate()" />
       </div>
-      <el-button color="#4284f2" size="large" @click="getPageData()">查找</el-button>
+      <el-button color="#4284f2" size="large" @click="getSchoolSearch()">查找</el-button>
     </div>
     <!-- Echarts 图表 -->
     <ComboCharts
+      v-if="chartStatus"
       :key="chartParams?.toString()"
       :schoolId="chartParams.school_id"
       :gradeId="chartParams.grade_id"
       :startTime="chartParams.start_time"
       :endTime="chartParams.end_time" />
+    <!-- 学生列表-->
+    <div class="student-container">
+      <!-- 学生查找 -->
+      <div class="student-search">
+        <el-input
+          v-model="studentParams.search"
+          placeholder="请输入学生名称或手机号码"
+          size="large"
+          @input="(value:string) => (studentParams.search = trimInput(value))" />
+        <el-button size="large" type="primary" @click="getStudentData()">查找</el-button>
+      </div>
+      <!-- 学生数据 -->
+      <div class="list-table">
+        <el-table :data="studentData" style="width: 100%">
+          <el-table-column align="center" label="序号" max-width="120" type="index" />
+          <el-table-column prop="name" label="学生名称" align="center" />
+          <el-table-column prop="grade_name" label="所在班级" align="center" />
+          <el-table-column prop="phone" label="手机号码" align="center" />
+          <el-table-column prop="create_time" label="注册时间" align="center" />
+          <el-table-column prop="count" label="训练次数" align="center" />
+          <el-table-column label="操作" align="center" min-width="160">
+            <template #default="scope">
+              <router-link :to="'/schoolBoard/training?id=' + scope.row.id" class="table-btn">训练报告</router-link>
+              <router-link v-if="scope.row.count > 16" :to="'/schoolBoard/result?id=' + scope.row.id" class="table-btn"
+                >训练效果分析</router-link
+              >
+              <button v-else class="table-btn disabled" @click="alertTrainingError()">训练效果分析</button>
+              <router-link
+                v-if="scope.row.count > 0"
+                :to="'/schoolBoard/evaluation?id=' + scope.row.id"
+                class="table-btn"
+                >测评数据对比</router-link
+              >
+              <button v-else class="table-btn disabled" @click="alertEvaluationError()">测评数据对比</button>
+            </template>
+          </el-table-column>
+          <!-- 无数据插槽 -->
+          <template #empty>
+            <div class="empty">
+              <img src="../../../assets/empty.png" alt="数据为空" />
+              <p>{{ dataMessage }}</p>
+            </div>
+          </template>
+        </el-table>
+        <pagination
+          v-if="studentCount > 0"
+          v-model:total="studentCount"
+          v-model:page="studentParams.page"
+          v-model:limit="studentParams.page_size"
+          @pagination="getStudentData()" />
+      </div>
+    </div>
   </div>
 </template>
 
@@ -143,10 +276,11 @@ onMounted(() => {
   }
 }
 
-:deep(.el-select),
-:deep(.el-date-editor) {
+:deep(.el-select) {
   --el-select-input-focus-border-color: none !important;
+}
 
+:deep(.search-box .el-date-editor) {
   width: 300px;
   margin: 0;
   overflow: hidden;
@@ -163,6 +297,8 @@ onMounted(() => {
 }
 
 :deep(.el-input__wrapper) {
+  background: #fff;
+  border-radius: 12px;
   box-shadow: none !important;
 }
 
@@ -174,7 +310,81 @@ onMounted(() => {
   box-shadow: none !important;
 }
 
+:deep(.el-select .el-input__wrapper.is-focus) {
+  box-shadow: none !important;
+}
+
+:deep(.el-table .el-table__header .el-table__cell .cell) {
+  overflow: visible;
+  white-space: nowrap;
+}
+
+:deep(.el-table th.el-table__cell) {
+  background: #e9ebee;
+}
+
 .mobile .el-col {
   margin-bottom: 10px;
 }
+
+.student-container {
+  position: relative;
+  padding: 20px 30px;
+}
+
+.student-search {
+  margin-bottom: 20px;
+  font-size: 16px;
+
+  .el-select {
+    width: 180px;
+    margin: 0 20px 0 0;
+  }
+
+  .el-input {
+    width: 250px;
+    margin: 0;
+  }
+
+  :deep(.el-input__inner) {
+    font-size: 16px;
+  }
+
+  .el-button {
+    padding: 0 26px;
+    margin: 0 20px;
+    font-size: 16px;
+    background: #4284f2;
+    border-radius: 10px;
+  }
+
+  b {
+    font-size: 20px;
+  }
+}
+
+.list-table {
+  overflow: hidden;
+  background: #fff;
+  border-radius: 25px;
+
+  .table-btn {
+    display: inline-block;
+    height: 38px;
+    padding: 0 15px;
+    margin-right: 10px;
+    line-height: 38px;
+    color: #fff;
+    background: #4284f2;
+    border-radius: 6px;
+
+    &.disabled {
+      background: #bfbfbf;
+    }
+  }
+}
+
+.empty {
+  padding: 200px 0;
+}
 </style>

+ 248 - 0
src/views/admin/training/index.vue

@@ -0,0 +1,248 @@
+<script setup lang="ts">
+import {useRouter} from "vue-router";
+import {useUserStore} from "@/store/modules/user";
+import {GradeList} from "@/api/grade/types";
+import {TrainingItem, TrainingParams} from "@/api/training/types";
+import {getGradeSelect} from "@/api/grade";
+import {getTrainingLists} from "@/api/training";
+import {getUrlParam, trimInput} from "@/utils";
+
+const router = useRouter();
+const userStore = useUserStore();
+defineOptions({
+  name: "TrainingIndex",
+  inheritAttrs: false,
+});
+
+const pageParams: TrainingParams = reactive({
+  school_id: userStore.schoolId,
+  is_formal: 1, // 1正式,2非正式
+  grade_id: 0,
+  page: 1,
+  page_size: 10,
+  // 学生名称/手机号
+  search: "",
+});
+const isFormal = ref(1);
+/**
+ * 班级数据
+ */
+const gradeData = ref<GradeList[]>();
+async function getGradeData(schoolId: number) {
+  getGradeSelect(schoolId)
+    .then(({data}) => {
+      gradeData.value = data;
+      gradeData.value?.unshift({id: 0, name: "全部班级"});
+    })
+    .catch((error) => {
+      gradeData.value = [];
+      gradeData.value?.unshift({id: 0, name: "全部班级"});
+      console.log(error.message);
+    });
+}
+const dataMessage = ref("加载中...");
+const trainingData = ref<TrainingItem[]>();
+const trainingCount = ref(0);
+async function getTrainingData(schoolId: number) {
+  pageParams.school_id = schoolId;
+  isFormal.value = pageParams.is_formal;
+  getTrainingLists(pageParams)
+    .then(({data}) => {
+      const {count, lists} = data;
+      trainingData.value = lists;
+      trainingCount.value = count;
+      if (!(count && count > 0 && lists.length > 0)) {
+        dataMessage.value = "没有符合搜索条件的记录!";
+        if (pageParams.is_formal == 2 && pageParams.search == "") {
+          dataMessage.value = "还没有任何体验用户训练记录!";
+        }
+        if (pageParams.is_formal == 1 && pageParams.grade_id == 0 && pageParams.search == "") {
+          dataMessage.value = "还没有任何学生训练记录!";
+        }
+      }
+    })
+    .catch((error) => {
+      dataMessage.value = error.message;
+      console.log(error.message);
+    });
+}
+function getTrainingSearch() {
+  const current = router.currentRoute.value.path;
+  router.push(current + "?page=" + pageParams.page + "&size=" + pageParams.page_size);
+  //getTrainingData(userStore.schoolId);
+}
+function alertError() {
+  ElMessage.error("本次训练采集的数据不足,无法分析并生成有效报告!");
+}
+onMounted(() => {
+  let page_no = getUrlParam("page");
+  if (page_no && Number(page_no) > 0) {
+    pageParams.page = Number(page_no);
+  }
+  let page_size = getUrlParam("size");
+  if (page_size && Number(page_size) > 0) {
+    pageParams.page_size = Number(page_size);
+  }
+  getGradeData(userStore.schoolId);
+  getTrainingData(userStore.schoolId);
+});
+// watch(
+//   () => userStore.schoolId,
+//   (newValue) => {
+//     // 学校切换后重新获取
+//     getGradeData(newValue);
+//     getTrainingData(newValue);
+//   }
+// );
+</script>
+
+<template>
+  <div class="training-container">
+    <!-- 学生查找 -->
+    <div class="training-search">
+      <el-select v-model="pageParams.is_formal" placeholder="正式学生/体验用户" size="large">
+        <el-option key="1" :value="Number(1)" label="正式学生" />
+        <el-option key="2" :value="Number(2)" label="体验用户" />
+      </el-select>
+      <el-select v-if="pageParams.is_formal == 1" v-model="pageParams.grade_id" placeholder="请选择班级" size="large">
+        <el-option v-for="item in gradeData" :key="item.id" :label="item.name" :value="item.id" />
+      </el-select>
+      <el-input
+        v-model="pageParams.search"
+        placeholder="请输入学生名称或手机号码"
+        size="large"
+        @input="(value:string) => (pageParams.search = trimInput(value))" />
+      <el-button size="large" type="primary" @click="getTrainingSearch()">查找</el-button>
+    </div>
+    <!-- 学生数据 -->
+    <div class="list-table">
+      <el-table :data="trainingData" style="width: 100%">
+        <el-table-column align="center" label="序号" max-width="120" type="index" />
+        <el-table-column prop="name" label="学生名称" align="center" />
+        <el-table-column v-if="isFormal == 1" prop="phone" label="手机号码" align="center" />
+        <el-table-column prop="device_name" label="训练方式" align="center" />
+        <el-table-column prop="create_time" label="训练时间" align="center" />
+        <el-table-column prop="play_time" label="课节" align="center" />
+        <el-table-column prop="play_time" label="训练时长" align="center" />
+        <el-table-column v-if="isFormal == 1" prop="grade_name" label="所在班级" align="center" />
+        <el-table-column label="操作" align="center" min-width="120">
+          <template #default="scope">
+            <template v-if="scope.row.report_status == 1">
+              <router-link
+                :to="
+                  '/schoolBoard/report?id=' +
+                  scope.row.game_record_id +
+                  '&type=' +
+                  scope.row.device_id +
+                  '&formal=' +
+                  isFormal
+                "
+                class="table-btn"
+                >报告详情</router-link
+              >
+            </template>
+            <button v-else class="table-btn disabled" @click="alertError()">报告详情</button>
+          </template>
+        </el-table-column>
+        <!-- 无数据插槽 -->
+        <template #empty>
+          <div class="empty">
+            <img src="../../../assets/empty.png" alt="数据为空" />
+            <p>{{ dataMessage }}</p>
+          </div>
+        </template>
+      </el-table>
+    </div>
+    <pagination
+      v-if="trainingCount > 0"
+      v-model:total="trainingCount"
+      v-model:page="pageParams.page"
+      v-model:limit="pageParams.page_size"
+      @pagination="getTrainingSearch()" />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.training-container {
+  position: relative;
+  padding: 20px 30px;
+}
+
+.training-search {
+  margin-bottom: 20px;
+  font-size: 16px;
+
+  .el-select {
+    width: 200px;
+    margin: 0 20px 0 0;
+  }
+
+  .el-input {
+    width: 250px;
+    margin: 0;
+  }
+
+  :deep(.el-input__inner) {
+    font-size: 16px;
+  }
+
+  .el-button {
+    padding: 0 26px;
+    margin: 0 20px;
+    font-size: 16px;
+    background: #4284f2;
+    border-radius: 10px;
+  }
+}
+
+:deep(.el-input__wrapper) {
+  background: #fff;
+  border-radius: 12px;
+  box-shadow: none !important;
+}
+
+:deep(.el-select) {
+  --el-select-input-focus-border-color: none !important;
+}
+
+:deep(.el-select .el-input__wrapper.is-focus) {
+  box-shadow: none !important;
+}
+
+:deep(.el-select:hover:not(.el-select--disabled) .el-input__wrapper) {
+  box-shadow: none !important;
+}
+
+:deep(.el-table .el-table__header .el-table__cell .cell) {
+  overflow: visible;
+  white-space: nowrap;
+}
+
+:deep(.el-table th.el-table__cell) {
+  background: #e9ebee;
+}
+
+.list-table {
+  overflow: hidden;
+  background: #fff;
+  border-radius: 25px;
+
+  .table-btn {
+    display: inline-block;
+    height: 38px;
+    padding: 0 15px;
+    line-height: 38px;
+    color: #fff;
+    background: #4284f2;
+    border-radius: 10px;
+
+    &.disabled {
+      background: #bfbfbf;
+    }
+  }
+}
+
+.empty {
+  padding: 200px 0;
+}
+</style>

+ 2 - 2
src/views/customer/dashboard/index.vue

@@ -170,7 +170,7 @@ watch(
                   font-size="30px"
                   :round-cap="Boolean(true)" />
                 <p>初期训练</p>
-                <p>专注力50以上人数比例</p>
+                <p>专注力50以上训练占比</p>
               </div>
             </el-col>
             <el-col :span="12">
@@ -187,7 +187,7 @@ watch(
                   font-size="30px"
                   :round-cap="Boolean(true)" />
                 <p>近期训练</p>
-                <p>专注力50以上人数比例</p>
+                <p>专注力50以上训练占比</p>
               </div>
             </el-col>
           </el-row>