فهرست منبع

feat: :sparkles: 学校级数据看板-报告列表

chaooo 1 سال پیش
والد
کامیت
08359f275e

+ 2 - 0
src/api/student/types.ts

@@ -36,6 +36,8 @@ export interface StudentManage {
 export interface StudentItem extends StudentList {
   // 学生名称
   create_time: string;
+  // 班级id
+  grade_id: number;
   // 班级名称
   grade_name: string;
 }

+ 13 - 1
src/api/training/index.ts

@@ -1,6 +1,6 @@
 import request from "@/utils/request";
 import {AxiosPromise} from "axios";
-import {TrainingManage, TrainingParams, TrainingResult} from "@/api/training/types";
+import {TrainingManage, TrainingParams, TrainingResult, TrainingStudentParams} from "@/api/training/types";
 
 /**
  * 训练记录
@@ -15,6 +15,18 @@ export function getTrainingLists(params: TrainingParams): AxiosPromise<TrainingM
 }
 
 /**
+ * 训练记录-学生
+ * /board/v1/games?school_id=95&is_formal=1&search=略&grade_id=35&page=1&page_size=10
+ */
+export function getTrainingListByStudent(params: TrainingStudentParams): AxiosPromise<TrainingManage> {
+  return request({
+    url: "/board/v1/game-list",
+    method: "get",
+    params: params,
+  });
+}
+
+/**
  * 训练报告
  * /board/v1/detail?game_record_id=82967
  */

+ 7 - 0
src/api/training/types.ts

@@ -12,6 +12,13 @@ export interface TrainingParams {
   page: number;
   page_size: number;
 }
+export interface TrainingStudentParams {
+  grade_id: number;
+  // 学生
+  baby_id: number;
+  page: number;
+  page_size: number;
+}
 
 /**
  * 训练记录列表

+ 0 - 35
src/views/admin/components/ComboCharts.vue

@@ -212,41 +212,6 @@ watchEffect(() => {
   </div>
   <div class="charts-container">
     <el-row :gutter="20">
-      <el-col :xs="24" :span="8">
-        <!-- <div class="charts-item">
-          <p class="title pos">学员专注力训练高专注占比分析</p>
-          <template v-if="lineStatus">
-            <AverageBarChart
-              id="averageBarChart1"
-              :key="averageData?.toString()"
-              :data-sets="averageData || [[], []]"
-              :data-max="lineChartData?.max_num || 10"
-              width="520px"
-              height="520px"
-              class="chart" />
-          </template>
-          <div v-else class="empty">
-            <img src="../../../assets/empty.png" alt="数据为空" />
-            <p>{{ lineMessage }}</p>
-          </div>
-          <el-row :gutter="15" class="bottom">
-            <el-col :span="12">
-              <p class="l">
-                <span>训练前期全体学员</span>
-                <span>高专注占比平均值</span>
-                <b>{{ lineChartData?.frontHeight }}</b>
-              </p>
-            </el-col>
-            <el-col :span="12">
-              <p class="r">
-                <span>训练后期全体学员</span>
-                <span>高专注占比平均值</span>
-                <b>{{ lineChartData?.afterHeight }}</b>
-              </p>
-            </el-col>
-          </el-row>
-        </div>-->
-      </el-col>
       <!-- 学员专注力评分分级占比分析 -->
       <el-col :xs="24" :span="16">
         <div class="charts-item">

+ 67 - 59
src/views/admin/school/index.vue

@@ -5,10 +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 {trimInput} from "@/utils";
+import {getUrlParam, trimInput} from "@/utils";
 import {StudentItem, StudentParams} from "@/api/student/types";
 import {getStudentLists} from "@/api/student";
 import {useRouter} from "vue-router";
+import SvgIcon from "@/components/SvgIcon/index.vue";
 
 const router = useRouter();
 
@@ -78,10 +79,14 @@ function getSchoolPageData() {
   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();
+  if (dataParams.school_id > 0) {
+    // 学生参数
+    studentParams.school_id = dataParams.school_id;
+    studentParams.grade_id = dataParams.grade_id;
+    getStudentData();
+  } else {
+    dataMessage.value = "你还未选择具体学校,请选择学校后查看。";
+  }
 }
 /**
  * 学生数据
@@ -96,6 +101,15 @@ const studentParams: StudentParams = reactive({
 const dataMessage = ref("加载中...");
 const studentCount = ref(0);
 const studentData = ref<StudentItem[]>();
+// 重置学生参数
+function initStudentParams() {
+  studentParams.school_id = dataParams.school_id;
+  studentParams.grade_id = dataParams.grade_id;
+  studentParams.search = "";
+  studentParams.page = 1;
+  studentParams.page_size = 10;
+  getStudentData();
+}
 async function getStudentData() {
   getStudentLists(studentParams)
     .then(({data}) => {
@@ -207,17 +221,22 @@ onMounted(() => {
       :gradeId="chartParams.grade_id"
       :startTime="chartParams.start_time"
       :endTime="chartParams.end_time" />
+    <!-- 学生查找 -->
+    <div v-if="dataParams.school_id > 0" class="search-box s2">
+      <el-input
+        v-model="studentParams.search"
+        placeholder="请输入学生名称或手机号码"
+        size="large"
+        @input="(value:string) => (studentParams.search = trimInput(value))" />
+      <el-button size="large" type="primary" @click="getSchoolSearch()">查找</el-button>
+      <el-button size="large" type="primary" @click="initStudentParams()">重置</el-button>
+      <a class="download-btn" href="javascript:void(0);">
+        <svg-icon icon-class="download" size="3rem" />
+        <span>报告原始数据下载</span>
+      </a>
+    </div>
     <!-- 学生列表-->
     <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="getSchoolSearch()">查找</el-button>
-      </div>
       <!-- 学生数据 -->
       <div class="list-table">
         <el-table :data="studentData" style="width: 100%">
@@ -229,24 +248,18 @@ onMounted(() => {
           <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>-->
-              <button class="table-btn" @click="pathTo('/schoolBoard/training?id=' + scope.row.id)">训练报告</button>
-              <!-- <router-link v-if="scope.row.count > 16" :to="'/schoolBoard/result?id=' + scope.row.id" class="table-btn"
-                >训练效果分析</router-link
-              >-->
+              <button
+                class="table-btn"
+                @click="pathTo('/schoolBoard/training?id=' + scope.row.id + '&grade=' + scope.row.grade_id)">
+                训练报告
+              </button>
               <button
                 v-if="scope.row.count > 16"
                 class="table-btn"
-                @click="pathTo('/schoolBoard/result?id=' + scope.row.id)">
+                @click="pathTo('/schoolBoard/result?id=' + scope.row.id + '&school=' + studentParams.school_id)">
                 训练效果分析
               </button>
               <button v-else class="table-btn disabled" @click="alertTrainingError()">训练效果分析</button>
-              <!-- <router-link
-                v-if="scope.row.count_report > 0"
-                :to="'/schoolBoard/evaluation?id=' + scope.row.id"
-                class="table-btn"
-                >测评数据对比</router-link
-              >-->
               <button
                 v-if="scope.row.count_report > 0"
                 class="table-btn"
@@ -285,9 +298,11 @@ onMounted(() => {
 }
 
 .search-box {
+  position: relative;
   display: flex;
   padding: 20px 55px;
   line-height: 40px;
+  background: #fff;
 
   .el-select {
     width: 140px;
@@ -296,10 +311,33 @@ onMounted(() => {
 
   .el-button {
     padding: 0 26px;
-    margin: 0 20px;
+    margin: 0 0 0 20px;
     font-size: 16px;
+    background: #4284f2;
     border-radius: 10px;
   }
+
+  .el-input {
+    width: 250px;
+    margin: 0;
+    border: 1px solid #ddd;
+    border-radius: 10px;
+  }
+
+  :deep(.el-input__inner) {
+    font-size: 16px;
+  }
+
+  .download-btn {
+    position: absolute;
+    right: 180px;
+    color: #4284f2;
+  }
+
+  .download-btn * {
+    margin-right: 12px;
+    vertical-align: text-bottom;
+  }
 }
 
 :deep(.el-select) {
@@ -315,7 +353,8 @@ onMounted(() => {
 }
 
 :deep(.search-box.s2 .el-select) {
-  border: none;
+  border: 1px solid #ddd;
+  border-radius: 10px;
 }
 
 :deep(.el-date-editor) {
@@ -358,37 +397,6 @@ onMounted(() => {
   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;

+ 38 - 144
src/views/admin/training/index.vue

@@ -1,65 +1,31 @@
 <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";
+import {TrainingItem, TrainingStudentParams} from "@/api/training/types";
+import {getTrainingListByStudent} from "@/api/training";
+import {getUrlParam} 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非正式
+const pageParams: TrainingStudentParams = reactive({
   grade_id: 0,
+  baby_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)
+async function getTrainingData() {
+  getTrainingListByStudent(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;
@@ -67,79 +33,61 @@ async function getTrainingData(schoolId: number) {
     });
 }
 function getTrainingSearch() {
-  const current = router.currentRoute.value.path;
-  router.push(current + "?page=" + pageParams.page + "&size=" + pageParams.page_size);
-  //getTrainingData(userStore.schoolId);
+  getTrainingData();
 }
 function alertError() {
   ElMessage.error("本次训练采集的数据不足,无法分析并生成有效报告!");
 }
+function pathTo(url: String) {
+  // 记录搜索条件
+  sessionStorage.setItem("studentsParams", JSON.stringify(pageParams));
+  // 路由跳转
+  router.push(url);
+}
 onMounted(() => {
-  let page_no = getUrlParam("page");
-  if (page_no && Number(page_no) > 0) {
-    pageParams.page = Number(page_no);
+  if (getUrlParam("id") && getUrlParam("id") * 1 > 0) {
+    pageParams.baby_id = getUrlParam("id") * 1;
   }
-  let page_size = getUrlParam("size");
-  if (page_size && Number(page_size) > 0) {
-    pageParams.page_size = Number(page_size);
+  if (getUrlParam("grade") && getUrlParam("grade") * 1 > 0) {
+    pageParams.grade_id = getUrlParam("grade") * 1;
   }
-  getGradeData(userStore.schoolId);
-  getTrainingData(userStore.schoolId);
+  // 获取记录的搜索条件
+  let params = sessionStorage.getItem("studentsParams");
+  if (params && params != "null") {
+    const parse: TrainingStudentParams = JSON.parse(params);
+    if (parse.page && parse.page > 0) {
+      pageParams.page = parse.page;
+    }
+    if (parse.page_size && parse.page_size > 0) {
+      pageParams.page_size = parse.page_size;
+    }
+  }
+  sessionStorage.removeItem("studentsParams");
+  getTrainingData();
 });
-// 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="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 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
-                "
+              <button
                 class="table-btn"
-                >报告详情</router-link
-              >
+                @click="pathTo('/schoolBoard/report?id=' + scope.row.game_record_id + '&type=' + scope.row.device_id)">
+                报告详情
+              </button>
             </template>
             <button v-else class="table-btn disabled" @click="alertError()">报告详情</button>
           </template>
@@ -168,60 +116,6 @@ onMounted(() => {
   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;

+ 5 - 8
src/views/customer/student/result.vue

@@ -61,16 +61,13 @@ function starElement(index: number, compare: number) {
 }
 
 onMounted(() => {
+  let schoolId: number = userStore.schoolId;
+  if (getUrlParam("school") && getUrlParam("school") * 1 > 0) {
+    schoolId = getUrlParam("school") * 1;
+  }
   // 图表数据
-  getChartData(userStore.schoolId);
+  getChartData(schoolId);
 });
-// watch(
-//   () => userStore.schoolId,
-//   (newValue) => {
-//     // 图表数据
-//     getChartData(newValue);
-//   }
-// );
 </script>
 
 <template>