浏览代码

feat: :sparkles: 学校级数据看板-导出Excel原始数据

chaooo 1 年之前
父节点
当前提交
5e67a2565c

+ 11 - 0
src/api/student/index.ts

@@ -23,3 +23,14 @@ export function getStudentBoard(id: number, school_id: number): AxiosPromise<Stu
     params: {school_id: school_id},
   });
 }
+
+/**
+ * 报告原始数据下载
+ */
+export function getStudentReportExcel(school_id: number): AxiosPromise<StudentBoard> {
+  return request({
+    url: "/board/v1/game-export",
+    method: "get",
+    params: {school_id: school_id},
+  });
+}

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

@@ -75,3 +75,25 @@ export interface StudentEEG {
   // 训练后
   after: number[];
 }
+
+/**
+ * 导出Excel原始数据
+ */
+export interface ReportExcel {
+  school_name: string;
+  grade_id: number;
+  grade_name: string;
+  play_time: number;
+  create_time: string;
+  name: string;
+  device_id: number;
+  device_name: string;
+  awaken: number;
+  height_value: number;
+  stable: number;
+  linemed_value: number;
+  scope_diff: number;
+  interfere: number;
+  att_average: number;
+  section: number;
+}

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

@@ -74,6 +74,11 @@ const adminRoutes: RouteRecordRaw[] = JSON.parse(
       ],
     },
     {
+      path: "/download/student/excel",
+      component: "admin/excel/index",
+      meta: {title: "导出Excel预览", name: "Export2Excel", hidden: true},
+    },
+    {
       path: "/download/student/result",
       component: "customer/student/download",
       meta: {title: "下载报告预览", name: "StudentDownload", hidden: true},

+ 252 - 0
src/views/admin/excel/Export2Excel.js

@@ -0,0 +1,252 @@
+/* eslint-disable */
+import * as XLSX from "xlsx";
+
+// TODO: this is a toy example, may be file-saver is a better choice
+// import { saveAs } from 'file-saver'
+function saveAs(blob, fileName) {
+  const type = fileName.split(".")[1];
+  console.log(type);
+  const file = new window.File([blob], fileName, {type: type});
+  console.log(file);
+  // 创建一个指向 File 对象的 URL
+  const url = URL.createObjectURL(file);
+
+  // 创建一个 a 标签
+  const a = document.createElement("a");
+  a.href = url;
+  a.download = fileName;
+
+  // 将 a 标签添加到文档中
+  document.body.appendChild(a);
+
+  // 模拟点击 a 标签,开始下载
+  a.click();
+
+  // 下载完成后,从文档中移除 a 标签,并释放 URL
+  document.body.removeChild(a);
+  URL.revokeObjectURL(url);
+  return file;
+}
+
+function generateArray(table) {
+  var out = [];
+  var rows = table.querySelectorAll("tr");
+  var ranges = [];
+  for (var R = 0; R < rows.length; ++R) {
+    var outRow = [];
+    var row = rows[R];
+    var columns = row.querySelectorAll("td");
+    for (var C = 0; C < columns.length; ++C) {
+      var cell = columns[C];
+      var colspan = cell.getAttribute("colspan");
+      var rowspan = cell.getAttribute("rowspan");
+      var cellValue = cell.innerText;
+      if (cellValue !== "" && cellValue === +cellValue) cellValue = +cellValue;
+
+      //Skip ranges
+      ranges.forEach(function (range) {
+        if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
+          for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
+        }
+      });
+
+      //Handle Row Span
+      if (rowspan || colspan) {
+        rowspan = rowspan || 1;
+        colspan = colspan || 1;
+        ranges.push({
+          s: {
+            r: R,
+            c: outRow.length,
+          },
+          e: {
+            r: R + rowspan - 1,
+            c: outRow.length + colspan - 1,
+          },
+        });
+      }
+
+      //Handle Value
+      outRow.push(cellValue !== "" ? cellValue : null);
+
+      //Handle Colspan
+      if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
+    }
+    out.push(outRow);
+  }
+  return [out, ranges];
+}
+
+function datenum(v, date1904) {
+  if (date1904) v += 1462;
+  var epoch = Date.parse(v);
+  return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
+}
+
+function sheet_from_array_of_arrays(data) {
+  var ws = {};
+  var range = {
+    s: {
+      c: 10000000,
+      r: 10000000,
+    },
+    e: {
+      c: 0,
+      r: 0,
+    },
+  };
+  for (var R = 0; R !== data.length; ++R) {
+    for (var C = 0; C !== data[R].length; ++C) {
+      if (range.s.r > R) range.s.r = R;
+      if (range.s.c > C) range.s.c = C;
+      if (range.e.r < R) range.e.r = R;
+      if (range.e.c < C) range.e.c = C;
+      var cell = {
+        v: data[R][C],
+      };
+      if (cell.v == null) continue;
+      var cell_ref = XLSX.utils.encode_cell({
+        c: C,
+        r: R,
+      });
+
+      if (typeof cell.v === "number") cell.t = "n";
+      else if (typeof cell.v === "boolean") cell.t = "b";
+      else if (cell.v instanceof Date) {
+        cell.t = "n";
+        cell.z = XLSX.SSF._table[14];
+        cell.v = datenum(cell.v);
+      } else cell.t = "s";
+
+      ws[cell_ref] = cell;
+    }
+  }
+  if (range.s.c < 10000000) ws["!ref"] = XLSX.utils.encode_range(range);
+  return ws;
+}
+
+function Workbook() {
+  if (!(this instanceof Workbook)) return new Workbook();
+  this.SheetNames = [];
+  this.Sheets = {};
+}
+
+function s2ab(s) {
+  var buf = new ArrayBuffer(s.length);
+  var view = new Uint8Array(buf);
+  for (var i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
+  return buf;
+}
+
+export function export_table_to_excel(id) {
+  var theTable = document.getElementById(id);
+  var oo = generateArray(theTable);
+  var ranges = oo[1];
+
+  /* original data */
+  var data = oo[0];
+  var ws_name = "SheetJS";
+
+  var wb = new Workbook(),
+    ws = sheet_from_array_of_arrays(data);
+
+  /* add ranges to worksheet */
+  // ws['!cols'] = ['apple', 'banan'];
+  ws["!merges"] = ranges;
+
+  /* add worksheet to workbook */
+  wb.SheetNames.push(ws_name);
+  wb.Sheets[ws_name] = ws;
+
+  var wbout = XLSX.write(wb, {
+    bookType: "xlsx",
+    bookSST: false,
+    type: "binary",
+  });
+
+  saveAs(
+    new Blob([s2ab(wbout)], {
+      type: "application/octet-stream",
+    }),
+    "test.xlsx"
+  );
+}
+
+export function export_json_to_excel({
+  multiHeader = [],
+  header,
+  data,
+  filename,
+  merges = [],
+  autoWidth = true,
+  bookType = "xlsx",
+} = {}) {
+  /* original data */
+  filename = filename || "excel-list";
+  data = [...data];
+  data.unshift(header);
+
+  for (let i = multiHeader.length - 1; i > -1; i--) {
+    data.unshift(multiHeader[i]);
+  }
+
+  var ws_name = "SheetJS";
+  var wb = new Workbook(),
+    ws = sheet_from_array_of_arrays(data);
+
+  if (merges.length > 0) {
+    if (!ws["!merges"]) ws["!merges"] = [];
+    merges.forEach((item) => {
+      ws["!merges"].push(XLSX.utils.decode_range(item));
+    });
+  }
+
+  if (autoWidth) {
+    /*设置worksheet每列的最大宽度*/
+    const colWidth = data.map((row) =>
+      row.map((val) => {
+        /*先判断是否为null/undefined*/
+        if (val == null) {
+          return {
+            wch: 10,
+          };
+        } else if (val.toString().charCodeAt(0) > 255) {
+          /*再判断是否为中文*/
+          return {
+            wch: val.toString().length * 2,
+          };
+        } else {
+          return {
+            wch: val.toString().length,
+          };
+        }
+      })
+    );
+    /*以第一行为初始值*/
+    let result = colWidth[0];
+    for (let i = 1; i < colWidth.length; i++) {
+      for (let j = 0; j < colWidth[i].length; j++) {
+        if (result[j]["wch"] < colWidth[i][j]["wch"]) {
+          result[j]["wch"] = colWidth[i][j]["wch"];
+        }
+      }
+    }
+    ws["!cols"] = result;
+  }
+
+  /* add worksheet to workbook */
+  wb.SheetNames.push(ws_name);
+  wb.Sheets[ws_name] = ws;
+
+  var wbout = XLSX.write(wb, {
+    bookType: bookType,
+    bookSST: false,
+    type: "binary",
+  });
+  saveAs(
+    new Blob([s2ab(wbout)], {
+      type: "application/octet-stream",
+    }),
+    `${filename}.${bookType}`
+  );
+}

+ 116 - 0
src/views/admin/excel/index.vue

@@ -0,0 +1,116 @@
+<template>
+  <div class="app-container">
+    <div class="empty">
+      <img src="../../../assets/empty.png" alt="数据为空" />
+      <p>{{ dataMessage }}</p>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import {getStudentReportExcel} from "@/api/student";
+import {getUrlParam} from "@/utils";
+import {ReportExcel} from "@/api/student/types";
+
+defineOptions({
+  name: "ComplexTable",
+  inheritAttrs: false,
+});
+
+const getList = (schoolId) => {
+  getStudentReportExcel(schoolId)
+    .then(({data}) => {
+      console.log(data);
+      list.value = data;
+      handleDownload();
+    })
+    .catch((error) => {
+      console.log(error.message);
+      dataMessage.value = error.message;
+    });
+};
+const dataMessage = ref("加载中...");
+const downloadLoading = ref(true);
+const handleDownload = () => {
+  downloadLoading.value = true;
+  import("./Export2Excel").then((excel) => {
+    const tHeader = [
+      "名字",
+      "学校名称",
+      "时长",
+      "时间",
+      "课节",
+      "设备",
+      "专注力平均值",
+      "专注力唤醒率",
+      "高专注占比",
+      "稳定度",
+      "和谐度",
+      "专注力维持区间",
+      "受干扰次数",
+    ];
+    const filterVal = [
+      "name",
+      "school_name",
+      "play_time",
+      "create_time",
+      "section",
+      "device_name",
+      "att_average",
+      "awaken",
+      "height_value",
+      "stable",
+      "linemed_value",
+      "scope_diff",
+      "interfere",
+    ];
+    const data = formatJson(filterVal);
+    excel.export_json_to_excel({
+      header: tHeader,
+      data,
+      filename: "报告原始数据",
+    });
+    downloadLoading.value = false;
+    window.history.back();
+  });
+};
+const list = ref<ReportExcel[]>([]);
+const formatJson = (filterVal: string[]) => {
+  return list.value.map((v: any) =>
+    filterVal.map((j: any) => {
+      // if (j === "create_time") {
+      //   return formatDate(v[j]);
+      // } else {
+      return v[j];
+      //}
+    })
+  );
+};
+// const formatDate = (timestamp: number) => {
+//   const date = new Date(timestamp);
+//   return date
+//     .toLocaleString("zh-CN", {
+//       year: "numeric",
+//       month: "2-digit",
+//       day: "2-digit",
+//       hour: "2-digit",
+//       minute: "2-digit",
+//       second: "2-digit",
+//       hour12: false,
+//     })
+//     .replace(/\//g, "-");
+// };
+
+onMounted(async () => {
+  let schoolId = 0;
+  if (getUrlParam("school") && getUrlParam("school") * 1 > 0) {
+    schoolId = getUrlParam("school") * 1;
+  }
+  getList(schoolId);
+});
+</script>
+<style lang="scss" scoped>
+.empty {
+  padding: 200px 0;
+}
+</style>

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

@@ -5,7 +5,7 @@ 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 {trimInput} from "@/utils";
 import {StudentItem, StudentParams} from "@/api/student/types";
 import {getStudentLists} from "@/api/student";
 import {useRouter} from "vue-router";
@@ -230,7 +230,10 @@ onMounted(() => {
         @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);">
+      <a
+        class="download-btn"
+        href="javascript:void(0);"
+        @click="pathTo('/download/student/excel?school=' + studentParams.school_id)">
         <svg-icon icon-class="download" size="3rem" />
         <span>报告原始数据下载</span>
       </a>
@@ -336,7 +339,7 @@ onMounted(() => {
 
   .download-btn * {
     margin-right: 12px;
-    vertical-align: text-bottom;
+    vertical-align: middle;
   }
 }