Explorar el Código

fix: 分割pdf图片

lvkun996 hace 4 meses
padre
commit
81802a9a83

+ 7 - 0
env.d.ts

@@ -0,0 +1,7 @@
+/// <reference types="vite/client" />
+
+declare module '*.vue' {
+  import type { DefineComponent } from 'vue'
+  const component: DefineComponent<{}, {}, any>
+  export default component
+}

+ 1 - 1
package.json

@@ -4,7 +4,7 @@
   "version": "0.0.0",
   "scripts": {
     "dev": "vite",
-    "build": "vue-tsc && vite build && node copy-plugins.js",
+    "build": "vite build  && node copy-plugins.js ",
     "preview": "vite preview",
     "format": "prettier --write src/",
     "upload:pro": "python upload.py /usr/share/nginx/html/luojigou/ report pro",

+ 112 - 0
src/api/center.ts

@@ -0,0 +1,112 @@
+import request from "@/utils/request";
+
+
+// 全年级观察记录分布
+export function getCenterBaseCount(data: {
+  "classIds": string[],
+	"gradeSemesterIds": string[],
+	"schoolId": string,
+	"teacherIds": string[],
+  year: number
+}) {
+  return request<any>({
+    url: `/teach/evaluation/statistics/base/count`,
+    method: "POST",
+    data,
+  });
+}
+
+
+
+// 班级记录
+export function getCenteClassRecord(data: {
+  "classIds": string[],
+	"gradeSemesterIds": string[],
+	"schoolId": string,
+	"teacherIds": string[],
+  year: number
+}) {
+  return request<any>({
+    url: `/teach/evaluation/statistics/class/record`,
+    method: "POST",
+    data,
+  });
+}
+
+
+// 各年级记录场景分布
+export function getCenteReportScene(data: {
+  "classIds": string[],
+	"gradeSemesterIds": string[],
+	"schoolId": string,
+	"teacherIds": string[],
+  year: number
+}) {
+  return request<any>({
+    url: `/teach/evaluation/statistics/report/scene`,
+    method: "POST",
+    data,
+  });
+}
+
+
+// 各年级各领域能力表现阶段汇总
+export function getCenteReportDmain(data: {
+  "classIds": string[],
+	"gradeSemesterIds": string[],
+	"schoolId": string,
+	"teacherIds": string[],
+  year: number
+}) {
+  return request<any>({
+    url: `/teach/evaluation/statistics/report/domain`,
+    method: "POST",
+    data,
+  });
+}
+
+// 所有老师观察记录分布明细
+export function getCenteTeacherRecord(data: {
+  "classIds": string[],
+	"gradeSemesterIds": string[],
+	"schoolId": string,
+	"teacherIds": string[],
+  year: number
+}) {
+  return request<any>({
+    url: `/teach/evaluation/statistics/teacher/record`,
+    method: "POST",
+    data,
+  });
+}
+
+// 所有学生各领域表现阶段明细
+export function getCenteStudentDomain(data: {
+  "classIds": string[],
+	"gradeSemesterIds": string[],
+	"schoolId": string,
+	"teacherIds": string[],
+  year: number
+}) {
+  return request<any>({
+    url: `/teach/evaluation/statistics/student/domain`,
+    method: "POST",
+    data,
+  });
+}
+
+
+// 各年级各领域能力表现阶段汇总
+export function getCenteGradeDomain(data: {
+  "classIds": string[],
+	"gradeSemesterIds": string[],
+	"schoolId": string,
+	"teacherIds": string[],
+  year: number
+}) {
+  return request<any>({
+    url: `/teach/evaluation/statistics/grade/domain`,
+    method: "POST",
+    data,
+  });
+}

BIN
src/assets/images/clazz-cover.png


BIN
src/assets/images/school-cover.png


+ 33 - 0
src/components/artAble.vue

@@ -0,0 +1,33 @@
+<template>
+<div class="scatter" >
+  <!-- !requestScatterCompelate &&  -->
+  <scatterCharts :datasource="scatterrData" v-if="scatterrData" />
+  <div class="scatter-desc" >
+    <van-icon name="info-o" style="margin-top: 2px;margin-right: 6px;" />
+    <div> 
+      根据所选时间内评估数据分析
+      幼儿在{{radarTitle}}- <span style="color: rgb(255 135 0);">{{ablitilyName}}</span>的平均进阶能力发展在
+      <span style="color: rgb(255 135 0);">{{scatterrData?.levelLabel}}</span>
+    </div>
+  </div>
+</div>
+</template>
+<script lang='ts'  setup >
+ import scatterCharts from '@/components/scatterCharts.vue'
+ 
+interface IProps {
+  scatterrData: {
+    levelLabel: ''
+  }
+  radarTitle: string
+  ablitilyName: string
+}
+
+const props = defineProps<IProps>()
+
+console.log(props);
+
+</script>
+<style lang='less' scoped >
+ 
+</style>

+ 51 - 0
src/components/highlightr.vue

@@ -0,0 +1,51 @@
+<template>
+<div class="highlightr" v-if="highlightrData && highlightrData.length !== 0" >
+  <!-- <div class="title" >
+    突出能力
+  </div> -->
+  <div class="highlightr-cover" >
+    <div class="highlightr-item" v-for="item in highlightrData" :key="item.id">
+      <img :src="item.iconUrl" alt="">
+      <div class="desc" >{{item.name}}</div>
+    </div>
+  </div>
+</div>
+
+</template>
+<script lang='ts'  setup >
+ 
+ interface IProps {
+  highlightrData: {iconUrl: string, name: string, id: string}[]
+ }
+
+ const props = defineProps<IProps>()
+ console.log(props);
+ 
+</script>
+<style lang='scss' scoped >
+.highlightr {
+  // margin: 48px 0;
+  .highlightr-cover {
+    display: flex;
+    flex-wrap: wrap;
+    margin-top: 12px;
+    .highlightr-item {
+      width: 64px;
+      background-color: #fefefe;
+      margin-right: 16px;
+      margin-bottom: 6px;
+      border-radius: 10px;
+      overflow: hidden;
+      img {
+        width: 64px;
+        height: 64px;
+      }
+      .desc {
+        font-size: 10px;
+        padding: 4px;
+        box-sizing: border-box;
+      }
+    }
+  }
+}
+</style>

+ 96 - 0
src/components/lineCharts.vue

@@ -0,0 +1,96 @@
+<template>
+  <div class="line">
+    <div ref="recordLine" :id="id" style="width: 100%;height: 100%;" > </div>
+  </div>
+</template>
+
+
+
+<script lang='ts'  setup >
+import * as echarts from 'echarts';
+import { useId } from 'flicker-vue-hooks';
+import { nextTick, ref, watch } from 'vue';
+
+const props = defineProps<{
+  datasource: any
+}>()
+
+
+const echartsOption = ref({
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow'
+    }
+  },
+  grid: {
+    top: '',
+    left: '1%',
+    right: '3%',
+    bottom: '1%',
+    containLabel: true
+  },
+  xAxis: {
+    type: 'value',
+    boundaryGap: [0, 0.01],
+    interval: 80,
+    axisLabel: {
+      formatter: function (value: any) {
+          return value.toFixed(0); // 保证显示整数
+      }
+    }
+  },
+  yAxis: {
+    type: 'category',
+    data: [],
+  },
+  series: [
+    {
+      name: '2011',
+      type: 'bar',
+      color: 'rgb(51, 179, 92)',
+      data: [1, 2, 3, 4, 5, 6]
+    }
+  ]
+});
+
+watch(
+  () => props.datasource, 
+  (newVal ) => {
+    // @ts-ignore
+    echartsOption.value.series[0].data = newVal.map(item => item.count)
+      // @ts-ignore
+    echartsOption.value.yAxis.data = newVal.map(item => item.name)
+    nextTick(() => {
+      initLineCharts(echartsOption.value)
+    })
+  }
+)
+
+const id = useId()
+
+const recordChart = ref()
+
+
+const initLineCharts = (options: any) => {
+  recordChart.value = echarts.init(document.getElementById(id));
+  recordChart.value.setOption(options);
+ }
+
+
+
+defineExpose({
+  getChartImg: () => recordChart.value.getDataURL() 
+})
+
+</script>
+
+
+<style lang="scss" scoped>
+.line {
+  width: 100%;
+  height: 250px;
+}
+</style>
+
+

+ 5 - 4
src/components/radarCharts.vue

@@ -31,7 +31,7 @@ const options: EChartsOption = {
   radar: {
       indicator: [],
       center: ['50%', '50%'],
-      radius: 120,
+      radius: 110,
       startAngle: 90,
       splitNumber: 8,
       shape: 'circle',
@@ -39,8 +39,9 @@ const options: EChartsOption = {
       axisName: {
         formatter: function (value, indicator) {
             //@ts-ignore
-            if (indicator.index === 0 || indicator.index === 4) return value
-
+            // if (indicator.index === 0 || indicator.index === 4) return value
+            console.log('indicator:', indicator);
+            
             return value?.split('').map( (chat, index) => (index + 1) % 3 === 0 ? '\n' + chat : chat ).join('')
         },
         color: '#428BD4',
@@ -119,7 +120,7 @@ defineExpose({
         // 导出的格式,可以是 'png', 'jpeg'
         type: 'png',
         // 导出的图片分辨率比例,默认是 1
-        pixelRatio: 1,
+        pixelRatio: 2,
         // 导出的图片背景色,默认是 'white'
         backgroundColor: '#ffffff'
     })

+ 14 - 3
src/components/scatterCharts.vue

@@ -52,8 +52,6 @@ const options = {
 };
 
 
-console.log('props.datasource.data:',  props.datasource.data);
-
 
 // @ts-ignore 
 options.series[0].data = props.datasource.data
@@ -68,12 +66,25 @@ onMounted(() => {
   nextTick(() => {
     useSchedulerOnce(() => {
       myChartRef.value = echarts.init(document.getElementById(id));
-      console.log('options:', options);
       myChartRef.value.setOption(options);
     })
   })
 })
 
+
+defineExpose({
+  getDataURL: () => {
+    return myChartRef.value.getDataURL({
+        // 导出的格式,可以是 'png', 'jpeg'
+        type: 'png',
+        // 导出的图片分辨率比例,默认是 1
+        pixelRatio: 2,
+        // 导出的图片背景色,默认是 'white'
+        backgroundColor: '#ffffff'
+    })
+  }
+})
+
 </script>
 <style lang='scss' scoped >
  .scatter {

+ 225 - 0
src/components/table.vue

@@ -0,0 +1,225 @@
+<template>
+  <div class="record-table" ref="recordTableDom" >
+
+    <div class="table-header">
+      <template v-for="col in props.columns" :key="col.key">
+        <!-- 普通表头 -->
+        <div class="header-cell">
+          <div  class="header-cell-title" :style="{width: col.width + 'px'}">{{ col.title }}</div>
+          <div v-if="col.child" class="child-titles" :style="{width: col.width + 'px'}">
+            <div class="child-title"  v-for="childCol in col.child" :key="childCol.key">
+              {{ childCol.title }}
+            </div>
+          </div>
+        </div>
+      </template>
+    </div>
+
+    <div class="table-row" v-for="(row, index) in props.data" :key="index">
+      <template v-for="col in props.columns" :key="col.key">
+        <div 
+          v-if="col.spanRow" 
+          class="table-row-cell"  
+          :style="{width: col.width + 'px',borderBottom: 'none', height: row.list.length * 34 + 'px', borderRight: '1px solid #eee' }"
+        >
+          {{ row[col.key] }}
+        </div>
+        <template v-if="props.columns[0].spanRow && !col.spanRow "> 
+          <div :style="{width: col.width + 'px',  flex:  col.width ? 2.9 : 1}" >
+            <div :class="['table-row-cell']"  v-for="(rowItem, i) in row.list" :key="i" >
+              {{ rowItem[col.key] }}
+            </div>
+          </div>
+        </template>
+        <div v-if="!col.child && !props.columns[0].spanRow" class="table-cell" :style="{width: col.width + 'px'}">
+          {{ row[col.key] }}
+        </div>
+        <template v-if="col.child && !props.columns[0].spanRow"> 
+          <div class="table-cell" :style="{width: col.width + 'px'}" >
+            <div v-for="childCol in col.child" :key="childCol.key" class="table-cell" :style="{width: col.width + 'px'}" >
+              {{ row[childCol.key] }}
+            </div>
+          </div>
+        </template>
+      </template>
+    </div>
+
+  </div>
+</template>
+
+<script setup lang="ts">
+/**eslint-disable */
+import html2canvas from 'html2canvas'
+import { ref } from "vue"
+import { useId } from 'flicker-vue-hooks'
+const props = defineProps({
+  data: {
+    type: Array,
+    default: () => []
+  },
+  columns: {
+    type: Array,
+    default: () => []
+  }
+})
+
+const recordTableDom = ref()
+const id = ref()
+
+const getChartImg = () => {
+  return new Promise((resolve) => {
+    console.log("id:", id);
+    
+
+    id.value = useId()
+
+    // html2canvas(document.querySelector('#'+ id.value)!).then(canvas => {
+    html2canvas(recordTableDom.value).then(canvas => {
+      resolve(canvas.toDataURL(''))
+    })
+  })
+}
+
+defineExpose({
+  getChartImg
+})
+
+
+// const 
+
+</script>
+
+
+<style scoped lang="scss">
+.record-table {
+  width: 100%;
+  border: 1px solid #eee;
+  
+  .table-header {
+    display: flex;
+    background-color: rgb(51, 179, 92);
+    color: #fff;
+    font-size: 12px;
+    .header-cell {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      flex-direction: column;
+      text-align: center;
+      font-weight: bold;
+      flex: 1;
+      border-right: 1px solid #eee;
+      box-sizing: border-box;
+      height: 58px;
+      .header-cell-title {
+        width: 100%;
+        box-sizing: border-box; 
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+      .child-titles {
+        display: flex;
+        width: 100%;
+        height: 29px;
+        .child-title {
+          width: 100%;
+          height: 34px;
+          line-height: 34px;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          border-top: 1px solid #eee;
+          border-right: 1px solid #eee;
+          box-sizing: border-box;
+        }
+      }
+      .child-titles .child-title:last-child {
+        border-right: none;
+      }
+      &:last-child {
+        border-right: none;
+      }
+
+      &.parent-cell {
+        display: flex;
+        flex-direction: column;
+        flex: 2; // 因为有两个子列,所以占用两倍宽度
+
+        .parent-title {
+          margin-bottom: 8px;
+          padding-bottom: 8px;
+          border-bottom: 1px solid rgba(255, 255, 255, 0.3);
+        }
+
+        .child-titles {
+          display: flex;
+          justify-content: space-around;
+
+          span {
+            flex: 1;
+            padding: 0 4px;
+
+            &:first-child {
+              border-right: 1px solid rgba(255, 255, 255, 0.3);
+            }
+          }
+        }
+      }
+    }
+  }
+  
+  .table-row {
+    display: flex;
+    border-top: 1px solid #eee;
+    
+    .table-cell {
+      // width: ;
+      flex: 1; 
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      text-align: center;
+      border-right: 1px solid #eee;
+      font-size: 14px;
+      height: 34px;
+      box-sizing: border-box;
+      line-height: 17px;
+      &:last-child {
+        border-right: none;
+      }
+    }
+
+    .table-row-cell {
+      // width: 100%;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      text-align: center;
+      border-bottom: 1px solid #eee;
+      font-size: 10px;
+      height: 34px;
+      box-sizing: border-box;
+      border-right: 1px solid #eee;
+      line-height: 12px;
+      &:last-child {
+        border-bottom: none;
+      }
+    } 
+
+    .width-100 {
+      width: 100%;
+    }
+
+    .flex-1 {
+      display: flex;
+      // flex: 1;
+      flex-wrap: wrap;
+    }
+    
+    &:hover {
+      background-color: #f5f7fa;
+    }
+  }
+}
+</style>

+ 8 - 9
src/main.ts

@@ -1,20 +1,19 @@
-import "@/styles/reset.scss";
+import router from "@/router";
+import store from "@/store";
 import "@/styles/common.scss";
 import "@/styles/fonts.scss";
+import "@/styles/reset.scss";
+import Vant from 'vant';
+import "vant/lib/index.css";
 import { createApp } from "vue";
 import App from "./App.vue";
-import router from "@/router";
-import store from "@/store";
-import "vant/lib/index.css";
-import Vant from 'vant';
 
 // import vconsole from "vconsole";
 // new vconsole();
 
 // @ts-ignore
-// window['injectData'] = function () {
-//   return 'injectData'
-  
-// }
+window['injectData'] = function () {
+  return 'injectData'
+}
 
 createApp(App).use(Vant).use(router).use(store).mount("#app");

+ 8 - 0
src/router/index.ts

@@ -79,6 +79,14 @@ const router = createRouter({
         title: "智慧观察评价报告",
       },
     },
+    {
+      path: "/customize/SmartCenter",
+      name: "SmartCenter",
+      component: () => import("@/views/customize/SmartCenter.vue"),
+      meta: {
+        title: "智慧中心",
+      }
+    },
   ],
 });
 

+ 4 - 3
src/store/index.ts

@@ -1,8 +1,9 @@
 import { createPinia } from "pinia";
-import useReportStore from "./modules/report";
+import useCenterStore from "./modules/center";
+import useClazzStore from "./modules/clazz";
 import useCustomizeStore from "./modules/customize";
+import useReportStore from "./modules/report";
 import useSummanyStore from "./modules/summany";
-import useClazzStore from "./modules/clazz";
 
-export { useReportStore, useCustomizeStore, useSummanyStore, useClazzStore };
+export { useCenterStore, useClazzStore, useCustomizeStore, useReportStore, useSummanyStore };
 export default createPinia();

+ 107 - 0
src/store/modules/center.ts

@@ -0,0 +1,107 @@
+import {
+  getCenteClassRecord,
+  getCenteGradeDomain,
+  getCenterBaseCount,
+  getCenteReportDmain,
+  getCenteReportScene,
+  getCenteStudentDomain, getCenteTeacherRecord
+} from '@/api/center';
+import {
+  defaultBaseCount, defaultClassRecord,
+  defaultGradeDomain,
+  defaultReportDomain,
+  defaultReportScene, defaultStudentDomain, defaultTeacherRecord
+} from '@/types/customize.d';
+import { defineStore } from "pinia";
+import { ref } from "vue";
+
+export default defineStore("center", () => {
+
+  const levelIdMap = ref(
+    new Map([
+      [-1, '托班'],
+      [0, '小班'],
+      [1, '中班'],
+      [2, '大班'],
+      [3, '毕业班'],
+    ])
+  )
+
+  const baseCount = ref(defaultBaseCount())
+
+  const classRecord = ref(defaultClassRecord())
+
+  const reportScene = ref(defaultReportScene())
+
+  const reportDomain = ref(defaultReportDomain())
+
+  const studentDomain = ref(defaultStudentDomain())
+
+  const teacherRecord = ref(defaultTeacherRecord())
+
+  const gradeDomain = ref(defaultGradeDomain())
+
+  const _getCenterBaseCount = async (params: any) => {
+    const { data } = await getCenterBaseCount(params)
+    baseCount.value = data
+  }
+
+  const _getCenteClassRecord = async (params: any) =>{
+    const { data } = await getCenteClassRecord(params)
+    classRecord.value = data.map(item => {
+      return {
+        ...item
+      }
+    })
+  }
+
+  const _getCenteReportScene = async (params: any) => {
+    const { data } = await getCenteReportScene(params)
+    reportScene.value = data
+  }
+
+  const _getCenteReportDmain = async (params: any) => {
+    const { data } = await getCenteReportDmain(params)
+    reportDomain.value = data
+  }
+
+  const _getCenteStudentDomain = async (params: any) => {
+    const { data } = await getCenteStudentDomain(params)
+    studentDomain.value = data
+  }
+
+  const _getCenteTeacherRecord = async (params: any) => {
+    const { data } = await getCenteTeacherRecord(params)
+    teacherRecord.value = data
+  }
+
+  const _getCenteGradeDomain = async (params: any) => {
+    const { data } = await getCenteGradeDomain(params)
+    gradeDomain.value = data.map(item => {
+      return {
+        ...item,
+        abilityList: item.abilityList
+      }
+    })
+    console.log('gradeDomain.value:', gradeDomain.value);
+    
+  }
+
+  return {
+    levelIdMap,
+    baseCount,
+    classRecord,
+    reportScene,
+    reportDomain,
+    studentDomain,
+    teacherRecord,
+    gradeDomain,
+    getCenterBaseCount: _getCenterBaseCount,
+    getCenteClassRecord: _getCenteClassRecord,
+    getCenteReportScene: _getCenteReportScene,
+    getCenteReportDmain: _getCenteReportDmain,
+    getCenteStudentDomain: _getCenteStudentDomain,
+    getCenteTeacherRecord: _getCenteTeacherRecord,
+    getCenteGradeDomain: _getCenteGradeDomain
+  }
+})

+ 2 - 2
src/store/modules/clazz.ts

@@ -1,7 +1,7 @@
+import { getClazzBase, getClazzDomain, getClazzScene, getStudentDomain } from '@/api/claszz';
+import dayjs from 'dayjs';
 import { defineStore } from "pinia";
-import { getClazzBase, getClazzDomain, getClazzScene, getStudentDomain   } from '@/api/claszz'
 import { ref } from "vue";
-import dayjs from 'dayjs'
 
 export default defineStore("clazz", () => {
 

+ 54 - 17
src/store/modules/customize.ts

@@ -1,7 +1,19 @@
-import { defineStore } from "pinia";
-import { ref } from "vue";
+import {
+  getBabyDetailRequest,
+  getBabyQuestionRecordPage,
+  getCustomizeBabyRecord,
+  getCustomizeSemesterBabies,
+  getCustomizeSingleRecord,
+  getDomainListRequest,
+  getQuestionRecordRequest,
+  getSemesterRecordRequest,
+  joinSemesterReportRequest,
+  sendSemesterRecordRequest,
+  sendSingleRecordRequest
+} from "@/api/customize";
 import {
   defaultBabyDetail,
+  defaultQuestionReport,
   defaultSemesterReport,
   defaultSingleRecord,
   IBabies,
@@ -11,23 +23,11 @@ import {
   IQuestionReport,
   IRecordData,
   ISemesterReport,
-  ISingleRecord,
-  defaultQuestionReport
+  ISingleRecord
 } from "@/types/customize.d";
-import {
-  getBabyDetailRequest,
-  getCustomizeBabyRecord,
-  getCustomizeSemesterBabies,
-  getCustomizeSingleRecord,
-  getDomainListRequest,
-  getSemesterRecordRequest,
-  getQuestionRecordRequest,
-  joinSemesterReportRequest,
-  sendSemesterRecordRequest,
-  sendSingleRecordRequest,
-  getBabyQuestionRecordPage
-} from "@/api/customize";
+import { defineStore } from "pinia";
 import { showToast } from "vant";
+import { ref } from "vue";
 
 export default defineStore("customize", () => {
   const singleRecord = ref<ISingleRecord>(defaultSingleRecord());
@@ -126,9 +126,46 @@ export default defineStore("customize", () => {
     semesterType: string;
   }) {
     const { status, data } = await getSemesterRecordRequest(params);
+    // const data = defaultSemesterReport()
+    // const status = 200
     if (status === 200) {
       /**eslint-disable */
       semesterReport.value = data;
+      semesterReport.value.domainDataList.forEach(item => {
+         item.recordList.forEach( childrenItem => {
+          if ( childrenItem.recordCount !== null ) {
+            const $par = {
+              xAxisData: [],
+              yAxisData: ['S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7', 'S8'],
+              data: [],
+              levelLabel: childrenItem.recordCount.levelLabel
+            }
+  
+          
+            childrenItem.recordCount.list.forEach( (recordCountItem: any) => {
+              // @ts-ignore
+              $par.xAxisData.push(recordCountItem.date)
+              // @ts-ignore
+              recordCountItem.list.forEach( _ => {
+                // @ts-ignore
+                $par.data.push({
+                  value: [recordCountItem.date, _.levelLabel],
+                  name: _.total
+                })
+              })
+            })
+  
+            // @ts-ignore
+            childrenItem.recordCount = {...$par, radarTitle:  '', title: childrenItem.abilityName}
+          } else {
+               // @ts-ignore
+            childrenItem.recordCount = {title: '', levelLabel: '', data: false}
+          }
+        })
+      })
+
+      console.log('semesterReport:', semesterReport.value);
+      
     }
   }
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 889 - 621
src/types/customize.d.ts


+ 272 - 11
src/utils/exportPdf.js

@@ -1,11 +1,10 @@
+import Bold from '@/assets/pdf/font/SourceHanSansCN-Bold.otf';
+import Regular from '@/assets/pdf/font/SourceHanSansCN-Regular.otf';
+import STXINGKA from '@/assets/pdf/font/STXINGKA.TTF';
 import pdfMake from "pdfmake/build/pdfmake";
-import pdfFonts from "pdfmake/build/vfs_fonts";
-import Regular from '@/assets/pdf/font/SourceHanSansCN-Regular.otf'
-import Bold from '@/assets/pdf/font/SourceHanSansCN-Bold.otf'
-import STXINGKA from '@/assets/pdf/font/STXINGKA.TTF'
 
-import { callAppFc, getRunPlatform } from '@/utils/index'
-import { dataDesc } from '@/utils/text'
+import { callAppFc, getRunPlatform } from '@/utils/index';
+import { dataDesc } from '@/utils/text';
 
 export const exportPDF = async (_semesterReport, progressCb) => {
 
@@ -52,7 +51,7 @@ export const exportPDF = async (_semesterReport, progressCb) => {
         { text: `${text}`, 
         fontSize: H3, 
         color: '#1a2c5e',
-        margin: [0, 16, 0, 0],
+        margin: [0, 16, 0, 16],
         bold: true
     })
 
@@ -350,8 +349,24 @@ export const exportPDF = async (_semesterReport, progressCb) => {
         ..._data.domainDataList.map( (item, index) => {
             const title = H2Template(item.domainName)
             const radarChart = radarImgTemplate(item.radarChartBase64)
+            const artAbleChart = []
+            if (item.highBase64) artAbleChart.push(...[H3Template('突出能力'), imgTemplate(item.highBase64)])
+        
             const recordList = []
-            item.recordList.map( record => {
+            item.recordList.map( record => {    
+
+                if (record.recordCount.data) {
+                    recordList.push(...[
+                        brakePage, H3Template('能力名称:' + record.abilityName), imgTemplate(record.artBase64),
+                        {   text: `${record.artDescription}`, 
+                            fontSize: H4,
+                            color: '#1a2c5e',
+                            alignment: 'center'
+                        },
+                        brakePage
+                    ])
+                }
+
                 recordList.push(H3Template('能力名称:' + record.abilityName))
                 recordList.push(H4Template('记录日期:' + record.recordDate))
                 // recordList.push(recordList.push(caclImgSplit(record.story.images)))
@@ -369,7 +384,7 @@ export const exportPDF = async (_semesterReport, progressCb) => {
 
             })  
 
-            const $result = [title, radarChart, ...recordList]
+            const $result = [title, radarChart, ...artAbleChart, ...recordList]
             if (index !== 0) $result.unshift(brakePage)
             return $result
         }).flat(2)
@@ -664,7 +679,6 @@ export const exportClazzPDF = async (clazzData, cb) => {
             imgTemplate(_clazzData.basePng),
             emptyTemplate(),
             H2Template("记录场景分布"),
-            
             imgTemplate(_clazzData.clazzDomainChart),
             emptyTemplate(),
             H2Template("观察领域分布"),
@@ -762,4 +776,251 @@ export const exportClazzPDF = async (clazzData, cb) => {
 
       generate()
     
-}
+}
+
+
+// 导出园所报告和年纪报告报
+export const exportSmartCenter = async (schoolData, cb) => {
+    const _schoolData = JSON.parse(JSON.stringify(schoolData))
+
+    const H1 = 30
+    const H2 = 20
+    const H3 = 14
+    const H4 = 12
+    const H5 = 10
+
+    const brakePage = {
+        text: " ",
+        margin: [0, 0, 0, 700]
+    }
+
+    
+    const emptyTemplate = () => (
+        { text: ``, 
+        fontSize: H5,
+        margin: [0, 10, 0, 20],
+        lineHeight: 1
+    }) 
+//         font: 'STXINGKA.TTF',
+    const H1Template = (text) => (
+        { text: `${text}`, 
+        fontSize: H1, 
+        margin: [0, 40, 0, 40], 
+        color: "blue",  
+        bold: true, 
+ 
+        alignment: 'left'
+    })
+
+    const H2Template = (text) => (
+        { text: `${text}`, 
+        color: '#1a2c5e',
+        fontSize: H2, 
+        margin: [0, 0, 0, 0],
+        bold: true
+    })
+
+    const H3Template = (text) => (
+        { text: `${text}`, 
+        fontSize: H3, 
+        color: '#1a2c5e',
+        margin: [0, 16, 0, 16],
+        bold: true,
+        alignment: 'left'
+    })
+
+    const imgTemplate = (url) => ({image: url, width: 500, alignment: 'center',fit: [950, 600], margin: [0, 20, 0, 0] } )
+
+    const imgTemplate1 = (url) => ({image: url, width: 500, alignment: 'center', margin: [0, 20, 0, 0] } )
+        
+    const coverTitle = (text) => (
+        { text: `${text}`, 
+        fontSize: 38, 
+        margin: [0, -120, 0, 4], 
+        color: "blue",  
+        bold: true,
+        alignment: 'left'
+    })
+
+
+    const cover =  {
+        image: 'cover',
+        fit: [700, 760],
+        alignment: 'center'
+    }
+
+    const createPdfContent = async () => {
+        const r =  await splitImageAndGeneratePDF(_schoolData.studentDomainDomImg, 710)
+        console.log('r:', r);
+
+        return [
+            cover,
+            brakePage,
+            coverTitle('评估结果与分析'),
+            { text: `1. 全园基本概况`, 
+              fontSize: H1, 
+              margin: [0, 12, 0, 12], 
+              color: "blue",  
+              bold: true, 
+              alignment: 'left'
+            },
+            H3Template(_schoolData.baseInfo),
+            H1Template("2. 各年级结果分析"),
+            H2Template("(1) 全年级观察记录分布"),
+            imgTemplate1(_schoolData.record),
+            brakePage,
+            H2Template("(2) 各班级观察记录明细"),
+            imgTemplate1(_schoolData.classRecord),
+            brakePage,
+            H2Template("(3) 各年级记录场景分布"),
+            _schoolData._reportScene.map(item =>[H3Template(item.title),  imgTemplate1(item.img), brakePage]),
+            brakePage,
+            H2Template("(4) 各年级观察领域分布"),
+            _schoolData._reportDomain.map(item =>[H3Template(item.title),  imgTemplate1(item.img), brakePage]),
+            brakePage,
+            H2Template("(5) 各年级各领域能力表现阶段汇总"),
+            _schoolData._gradeDomainDom.map(item =>[H3Template(item.title),  imgTemplate(item.img), brakePage]),
+            brakePage,
+            H1Template("3. 全园教师及学生数据分析"),
+            H2Template("(1) 所有老师观察记录分布明细"),
+            imgTemplate1(_schoolData.teacherRecordDomImg),
+            brakePage,
+            H2Template("(2) 所有学生各领域表现阶段明细"),
+            emptyTemplate(),
+            ...r
+            // imgTemplate(_schoolData.studentDomainDomImg),
+        ]
+    }
+
+
+    function splitImageAndGeneratePDF(imageSrc, pageHeight) {
+        return new Promise(resove => {
+            const img = new Image();
+            img.src = imageSrc;
+            img.onload = function () {
+              const canvas = document.createElement('canvas');
+              const ctx = canvas.getContext('2d');
+          
+              const imgWidth = img.width;
+              const imgHeight = img.height;
+              const scale = imgWidth / 500; // 调整图片宽度为 500(适配 PDF 页面)
+              const scaledHeight = imgHeight / scale;
+          
+              canvas.width = 500;
+              canvas.height = pageHeight;
+    
+              const r =  []
+          
+              let yOffset = 0;
+              while (yOffset < scaledHeight) {
+                ctx.clearRect(0, 0, canvas.width, canvas.height);
+                ctx.drawImage(
+                  img,
+                  0,
+                  yOffset * scale,
+                  imgWidth,
+                  Math.min(pageHeight * scale, imgHeight - yOffset * scale),
+                  0,
+                  0,
+                  canvas.width,
+                  canvas.height
+                );
+          
+                const dataUrl = canvas.toDataURL();
+                r.push({
+                  image: dataUrl,
+                  width: 500,
+                  margin: [0, 0, 0, 10],
+                });
+          
+                yOffset += pageHeight;
+              }
+    
+              resove(r)
+          
+            };
+        })
+    }
+
+    async function generate() {
+
+        const pdfContent = {
+            pageBreakBefore: false,
+            content: null,
+            images: {
+                cover:  _schoolData.cover, 
+                deg: "https://img.luojigou.vip/FvpoQUenV0XVm1U_DxONnfBIq0nR.png",
+                dot: "https://img.luojigou.vip/FjMe6awMO18PnKxhs5mHnmmvUKrt.png",
+            },
+            defaultStyle: {
+                font: 'SourceHanSans.ttf'
+            },
+            pageSize: 'A4',
+            pageMargins: [40, 30, 40, 30],
+            footer: function (currentPage, pageCount) {
+                return [
+                    { text: currentPage.toString() + ' / ' + pageCount, alignment: 'center' }
+                ]
+            }
+        };
+      
+          pdfContent.content = await createPdfContent()
+
+  
+        // 下载进度回调函数
+          const progressCallback = (progress) => cb(progress)
+  
+          pdfMake.setProgressCallback(progressCallback);
+          console.log('下载进度回调函数');
+            
+          if (getRunPlatform() === 'app') {
+            console.log('getRunPlatform:app');
+              pdfMake.createPdf(pdfContent).getBase64().then(data => {
+                console.log('pdf生成完成');
+                  callAppFc('saveFile', {
+                      fileName: new Date().getTime() + '.pdf', 
+                      data: `data:application/pdf;base64,` + data, 
+                      callback: () => {}}
+                  )
+              })
+          } else {
+
+              pdfMake.createPdf(pdfContent).download();
+          }
+       
+    }
+
+    const isDev = import.meta.env.MODE === 'development'
+
+    const prefix = isDev ? location.origin + '/src/static' : `https://luojigou.vip/report/static`
+
+   pdfMake.fonts = isDev?  {
+     "SourceHanSans.ttf": {
+         normal: prefix + '/SourceHanSansCN-Regular.otf',
+         bold: prefix + '/SourceHanSansCN-Bold.otf',
+         italics: prefix+ '/SourceHanSansCN-Regular.otf',
+         bolditalics:prefix + '/SourceHanSansCN-Bold.otf'
+     },
+     "STXINGKA.TTF": {
+         normal:  prefix + '/STXINGKA.TTF',
+         bold:  prefix + '/STXINGKA.TTF',
+         italics: prefix + '/STXINGKA.TTF',
+         bolditalics:  prefix + '/STXINGKA.TTF',
+     }
+  } : {
+         "SourceHanSans.ttf": {
+             normal: Regular,
+             bold: Bold,
+             italics: Regular,
+             bolditalics: Bold
+         },
+         "STXINGKA.TTF": {
+             normal: STXINGKA,
+             bold:  STXINGKA,
+             italics: STXINGKA,
+             bolditalics: STXINGKA,
+         }
+     }
+
+    generate()
+}

+ 3 - 1
src/utils/index.ts

@@ -109,11 +109,13 @@ export function getAppToken() {
   if (token?.length) {
     return token;
   }
-  return new Promise<string>((resolve) => {
+  return new Promise<string>((resolve, reject) => {
     (window as any).getToken = (token: string) => {
       if (token) {
         setToken(token);
         resolve(token);
+      } else {
+        reject(false)
       }
     };
   });

+ 11 - 4
src/views/customize/ClazzReport.vue

@@ -52,7 +52,7 @@
         </div>
         <div class="record-scene" >
           <div class="line">
-            <div ref="recordLine" style="width: 100%;height: 100%;" > </div>
+            <div ref="observeLine" style="width: 100%;height: 100%;" > </div>
           </div>
         </div>
       </div>
@@ -62,7 +62,7 @@
         </div>
         <div class="observe-scene" >
           <div class="line">
-            <div ref="observeLine" style="width: 100%;height: 100%;" > </div>
+            <div ref="recordLine" style="width: 100%;height: 100%;" > </div>
           </div>
         </div>
       </div>
@@ -175,6 +175,7 @@ import dayjs from 'dayjs'
 import { exportClazzPDF } from '@/utils/exportPdf'
 import html2canvas from 'html2canvas'
 import { dataDesc } from '@/utils/text'
+import Table from '@/components/table.vue'
 
 const { p } = useRoute().query;
 
@@ -242,7 +243,7 @@ const { p } = useRoute().query;
   xAxis: {
     type: 'value',
     boundaryGap: [0, 0.01],
-    interval: 1,
+    interval: 10,
     axisLabel: {
       formatter: function (value) {
           return value.toFixed(0); // 保证显示整数
@@ -258,7 +259,7 @@ const { p } = useRoute().query;
       name: '2011',
       type: 'bar',
       color: 'rgb(51, 179, 92)',
-      data: [0, 1, 2, 4, 8, 10, 12]
+      data: []
     }
   ]
  };
@@ -335,9 +336,13 @@ const { p } = useRoute().query;
  onMounted( async () => {
   await getAppToken()
   // await getTestTeachToken()
+  console.log(p);
+  
   const $par = JSON.parse(decodeURIComponent(atob(p)))
   // const $par = JSON.parse(decodeURIComponent(atob('eyJjbGFzc0lkcyI6WyIxNzc2OTA4Mjc2MDMxOTkxODEwIl0sImdyYWRlU2VtZXN0ZXJJZHMiOlsiMDAiLCIxMSIsIjAxIiwiLTEwIiwiLTExIiwiMjAiLCIxMCIsIjIxIl0sInNjaG9vbElkIjoiMTc3NjkwNzcwMzQyOTgwNDAzMyIsInRlYWNoZXJJZHMiOlsiMTc3NjkxMTk2MDU2MDI4MzY1MCIsIjE3NzY5MDk4MzQ2ODU2ODk4NTciLCIxNzc2OTEyMDgwOTk1NTI4NzA2IiwiMTUxMTk3NTE1NjI0OTQ1MjU0NiJdfQ==')))
   // const $par = {"classIds":["1655822889577734146"],"gradeSemesterIds":["00","11","01","-10","-11","20","10","21"],"schoolId":"1304705665728421889","teacherIds":["1450638512771702786","1436187006844682241","1304705367924449281","1711187837184294914","1800414891962654721","1778693522847088641"]}
+  console.log('$par', $par);
+  
   getClazzBase($par)
   getClazzDomain($par).then(() => {
 
@@ -349,6 +354,8 @@ const { p } = useRoute().query;
     $options.yAxis.data =  clazzDomainData.value[0]?.domains.map(item => item.name)
      //@ts-ignore
     $options.series[0].data =  clazzDomainData.value[0]?.domains.map(item => item.count)
+    console.log('clazzDomainData.value[0]?.domains.map(item => item.count):', clazzDomainData.value[0]?.domains.map(item => item.count));
+    // $options.series[0].data = [28]
     initRcordLineCharts($options)
    })
   })

+ 121 - 6
src/views/customize/SemesterReport.vue

@@ -243,15 +243,44 @@
           <div v-if="abilityIndex === 0" style="display: flex;align-items: center;justify-content: center;"> 
             <radarCharts :ref="el => radarListDom[index] = el"  :datasource="item.radarChart" /> 
           </div>
+          
+            <!-- 突出能力  -->
+            <div class="highlightr-able-title"  v-if="abilityIndex === 0 && item.highlightAbilities.length > 0" >
+              突出能力
+            </div>
+            <div class="highlightr-able" :id="'highlightr-able-' + index"   v-if="abilityIndex === 0 && item.highlightAbilities.length > 0 ">
+              <highlightr :highlightrData="item.highlightAbilities" />
+            </div>
+         
           <div class="ability-title flex-center">
             <img :src="ability.abilityIconUrl" alt="" class="ability-title-logo" />
             <div class="ability-title-text">{{ ability.abilityName }}</div>
           </div>
-          <div class="container">
+
+          <div class="scatter" v-if="ability.recordCount !== null && ability.recordCount.data "  >
+              <scatterCharts 
+                :ref="el => formatData(ability, el)"
+                :datasource="ability.recordCount"
+                v-if="ability.recordCount !== null && ability.recordCount.data "  
+              />
+            <div class="scatter-desc"  >
+              <van-icon name="info-o" style="margin-top: 2px;margin-right: 6px;" />
+              <div> 
+                根据所选时间内评估数据分析
+                幼儿在<span style="color: rgb(255 135 0);">{{ability.abilityName}}</span>的平均进阶能力发展在
+                <span style="color: rgb(255 135 0);">{{ability.recordCount?.levelLabel}}</span>
+              </div>
+            </div>
+           </div>
+
             <div class="ability-time">
               <img :src="getImageUrl('clock_logo')" alt="" class="ability-time-logo" />
               <div class="ability-time-text">记录时间:{{ ability.recordDate }}</div>
             </div>
+
+          
+          <div class="container">
+            
             <img :src="getImageUrl('observation_records')" alt="" class="ability-name" />
             <div class="ability-content">
               <div v-if="isHas(ability.story.images)" class="ability-content-images">
@@ -324,10 +353,13 @@ import { computed, onMounted, onUnmounted, ref, watch } from "vue";
 import ShareModal from "@/views/customize/components/ShareModal.vue";
 // @ts-ignore
 import { exportPDF } from '@/utils/exportPdf'
-
+import scatterCharts from  '@/components/scatterCharts.vue'
 import radarCharts from  '@/components/radarCharts.vue'
 import {nextTick } from 'vue'
 import exportButton from '@/components/exportButton.vue'
+import highlightr from  '@/components/highlightr.vue'
+import html2canvas from 'html2canvas'
+
 
 const {
   b: babyId,
@@ -356,6 +388,8 @@ const opacity = ref(0);
 // }
 
 const radarListDom = ref<any[]>([])
+   // @ts-ignore
+const artListDom = ref<any[]>([])
 
 const semesterReport = computed(() => {
   return customizeStore.semesterReport;
@@ -472,17 +506,58 @@ function getTargetStyle(index: number) {
   return style;
 }
 
+
+const elementToBase64 = (el: HTMLElement) => {
+  return new Promise((resolve) => {
+    html2canvas(el, {useCORS: true}).then(canvas => {
+      const png = canvas.toDataURL()
+      resolve(png)
+    })
+  })
+}
+
+   // @ts-ignore
+const formatData = (ability, el) => {
+  
+  ability.artBase64 = el
+  ability.el = el
+  ability.artDescription = `根据所选时间内评估数据分析 幼儿在${ability.abilityName}的平均进阶能力发展在${ability.recordCount?.levelLabel}`
+}
+
 const loading = ref(false)
 
 const exportReport = () => {
   loading.value = true
-  nextTick(() => {
+  nextTick( async () => {
+      // highlightr-able
     // @ts-ignore
     semesterReport.value.radarChartBase64 = radarListDom.value[-1].getDataURL()
-    semesterReport.value.domainDataList.forEach((item, index) => {
+    console.log('semesterReport:',semesterReport);
+    
+    
+    for (let index = 0; index < semesterReport.value!.domainDataList.length; index++) {
+
+      const item = semesterReport.value!.domainDataList[index];
+      console.log('item:', item);
+      
       // @ts-ignore
       item.radarChartBase64 = radarListDom.value[index].getDataURL()
-    })
+      
+      for (let i = 0; i < item.recordList.length; i++) {
+        const childrenItem = item.recordList[i];
+        console.log('childrenItem:',childrenItem);
+            // @ts-ignore
+        childrenItem.artBase64 = childrenItem.el && childrenItem.el.getDataURL()
+      }
+      
+      if (document.getElementById('highlightr-able-' + index)) {
+         // @ts-ignore
+        item.highBase64 = await elementToBase64(document.getElementById('highlightr-able-' + index)!)
+      }
+    }
+
+    console.log(' semesterReport.value:',  semesterReport.value);
+    
 
     exportPDF(semesterReport.value, async (progress: number) => {
       const r = (progress * 100).toFixed(0) 
@@ -496,6 +571,7 @@ const exportReport = () => {
 }
 
 onMounted(async () => {
+  // await getSemesterRecord({ babyId, classId, classLevelCode, semesterType });
   if (
     typeof babyId === "string" &&
     typeof classId === "string" &&
@@ -1406,7 +1482,46 @@ onUnmounted(() => {
       border-radius: 17px 17px 17px 17px;
       box-sizing: border-box;
       overflow: hidden;
-
+      .highlightr-able-title {
+        width: 100%;
+        padding-left: 12px;
+        margin-bottom: 24px;
+      }
+      .highlightr-able {
+        width: 100%;
+        margin: 24px 0;
+        margin: 0 auto;
+        background-color: #f6f8ff;
+        padding: 6px 12px;
+        box-sizing: border-box;
+        margin-bottom: 24px;
+      }
+      .scatter-name {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+      }
+      .scatter {
+        width: 94%;
+        margin: 24px 0;
+        margin: 0 auto;
+        margin-top: 24px;
+        border-radius: 20px;
+        background-color:  rgb(255 232 206);
+        box-sizing: border-box;
+        padding-bottom: 12px;
+        .scatter-desc {
+          padding: 0 24px;
+          box-sizing: border-box;
+          display: flex;
+          align-items: start;
+          margin-top: -0;
+          div {
+            font-size: 14px;
+          }
+        }
+      }
       &-title {
         margin-top: 20px;
 

+ 483 - 0
src/views/customize/SmartCenter.vue

@@ -0,0 +1,483 @@
+<template>
+    <HeaderPart ></HeaderPart>
+  <div class="smart-center">
+    <div class="smart-center-cover" id='cover' :style="{'backgroundImage': type == 1 ? `url(${getImageUrl('school-cover')})` : `url(${getImageUrl('clazz-cover')})`}" >
+      <div class="smart-center-name">
+        {{baseCount.schoolName}}
+      </div>
+      <div class="smart-center-date">
+        <div class="smart-center-date-title" >成长阶段:</div>
+        <div class="smart-center-date-value" >{{baseCount.dateRange}}</div>
+      </div>
+    </div>
+    <!-- 评估结果与分析 -->
+    <div class="smart-center-analysis">
+      <div class="smart-center-analysis-title">
+        评估结果与分析
+      </div>
+      <div class="smart-center-analysis-item-1" >
+         <div class="title-2" >
+          1. 全园基本概况
+         </div>
+         <div class="smart-center-analysis-item-1-content title-3" >
+          {{baseCount.schoolName}}班级{{baseCount.classCount}}个,共有学生{{baseCount.behavior}}名,在本评估周期内共统计到教师的观察记录{{baseCount.behavior}}条,
+          观察记录到学生{{baseCount.done.length}}名,未观察到的学生{{baseCount.notDone.length}}名。
+         </div>
+      </div>
+      <!-- (1)全年级观察记录分布 -->
+      <div class="smart-center-analysis-item-1" >
+          <div class="title-2" >
+            2. 各年级结果分析
+          </div>
+          <div class="title-4" >
+            (1)全年级观察记录分布
+          </div>
+          <div class="smart-center-analysis-item-2-content" >
+            <div class="base-table" id="capture" >
+              <div class="base-table-item">
+                <div class="title" >记录总数</div>
+                <div class="value" >{{baseCount.records}}篇</div>
+              </div>
+              <div class="base-table-item">
+                <div class="title" >行为记录</div>
+                <div class="value" >{{baseCount.behavior}}次</div>
+              </div>
+          </div>
+          </div>
+        </div>
+        <!-- (2)各班级观察记录明细 -->
+        <div class="smart-center-analysis-item-3" >
+          <div class="title-4" >
+            (2)各班级观察记录明细
+          </div>
+          <div class="smart-center-analysis-item-3-content"   >
+            <Table ref="tableDom" :columns="classColumns" :data="classRecord"  />
+          </div>
+        </div>
+        <!-- (3)各年级记录场景分布 -->
+        <div class="smart-center-analysis-item-3" >
+          <div class="title-4" >
+            (3)各年级记录场景分布
+          </div>
+          <div class="scene" v-for="(item, index) in reportScene" :key="item.levelId" >
+            <div class="picture-title" >
+              {{levelIdMap.get(item.levelId)}}记录场景分布
+            </div>
+            <div class="smart-center-analysis-item-3-content" >
+              <LineCharts :ref="el => reportSceneDom[index]= {title: levelIdMap.get(item.levelId) + '记录场景分布', el}"  :datasource="item.scenes"  />
+            </div>
+          </div>
+        </div>
+        <!-- (4)各年级观察领域分布 -->
+        <div class="smart-center-analysis-item-3" >
+          <div class="title-4" >
+            (4). 各年级观察领域分布
+          </div>
+          <div class="area" v-for="item in reportDomain" :key="item.levelId" >
+            <div class="picture-title" >
+              {{levelIdMap.get(item.levelId)}}记录领域分布
+            </div>
+            <div class="smart-center-analysis-item-3-content" >
+              <LineCharts 
+                :ref="el => reportDomainDom[item.levelId] = {title: levelIdMap.get(item.levelId) + '记录领域分布', el}"   
+                :datasource="item.domains" 
+              />
+            </div>
+          </div>
+        </div>
+        <!-- (5)各年级各领域能力表现阶段汇总 -->
+        <div class="smart-center-analysis-item-3" >
+          <div class="title-4" >
+            (5). 各年级各领域能力表现阶段汇总
+          </div>
+          <div class="able" v-for="item in gradeDomain" :key="item.classLevelCode" >
+            <div class="picture-title" >
+              {{levelIdMap.get(item.classLevelCode)}}各领域能力表现阶段汇总
+            </div>
+            <div class="smart-center-analysis-item-3-content" >
+              <Table 
+                :ref="el => gradeDomainDom[item.classLevelCode] = {title: levelIdMap.get(item.classLevelCode) + '各领域能力表现阶段汇总', el}" 
+                :columns="testColumns"  
+                :data="item.abilityList" 
+              />
+            </div>
+          </div>
+        </div>
+        <!-- 3. 全园教师及学生数据分析 -->
+        <div class="smart-center-analysis-item-1" >
+          <div class="title-2" >
+            3. 全园教师及学生数据分析
+          </div>
+          <div class="title-4" >
+            (1)所有老师观察记录分布明细
+          </div>
+          <div class="smart-center-analysis-item-3-content" >
+            <Table ref="teacherRecordDom" :columns="teacherColumns"  :data="teacherRecord"  />
+          </div>
+          <div class="title-4" >
+            (2)所有学生各领域表现阶段明细
+          </div>
+          <div class="smart-center-analysis-item-3-content" >
+            <Table ref="studentDomainDom" :columns="studentColumns"  :data="studentDomain"  />
+          </div>
+        </div>
+      </div>
+
+    <exportButton :loading="loading" @export="exportReport" />
+  </div>
+</template>
+
+
+<script setup lang="ts">
+// @ts-ignore
+import HeaderPart from './components/HeaderPart.vue'
+import exportButton from '@/components/exportButton.vue';
+import LineCharts from '@/components/lineCharts.vue';
+import Table from '@/components/table.vue';
+import { useCenterStore } from "@/store/index.js";
+import { getImageUrl, getAppToken } from "@/utils";
+  // @ts-ignore
+import { exportSmartCenter } from "@/utils/exportPdf.js";
+import { storeToRefs } from "pinia";
+import { onMounted, ref } from 'vue';
+import { useRoute } from "vue-router";
+import html2canvas from 'html2canvas'
+const { p, type } = useRoute().query!;
+console.log('路由参数:', type);
+
+const centerStore = useCenterStore();
+const { baseCount, classRecord, reportDomain, reportScene, levelIdMap, studentDomain, teacherRecord, gradeDomain } = storeToRefs(centerStore);
+const { getCenterBaseCount, getCenteClassRecord, getCenteReportDmain, getCenteReportScene,
+  getCenteStudentDomain, getCenteTeacherRecord, getCenteGradeDomain
+ } = centerStore;
+
+
+
+const tableDom = ref()
+
+const reportSceneDom = ref({})
+
+const reportDomainDom = ref({})
+
+const gradeDomainDom = ref({})
+
+const teacherRecordDom = ref()
+
+const studentDomainDom = ref()
+
+const classColumns = ref([
+  {title: '班级', key: 'className'},
+  {title: '教师数量', key: 'teacher'},
+  {title: '记录总数', key: 'records'},
+  {title: '观察学生总数', key: 'done'},
+  {title: '未观察人数', key: 'notDone'},
+])
+  // @ts-ignore
+const gradeDomainColumns = ref([
+  {title: '领域', key: 'domainName'},
+  {title: '能力', key: 'abilityName'},
+  {title: 's1', key: 's1'},
+  {title: 's2', key: 's2'},
+  {title: 's3', key: 's3'},
+  {title: 's4', key: 's4'},
+  {title: 's5', key: 's5'},
+  {title: 's6', key: 's6'},
+  {title: 's7', key: 's7'},
+  {title: 's8', key: 's8'}
+])
+
+const teacherColumns = ref([
+  {  title: '教师',   key: 'teacherName' },
+  {  title: '观察记录数',   key: 'education' },
+  {  title: '观察学生数量', key: 'students' },
+  {  title: '未记录', key: 'tactics' }
+])
+
+const studentColumns = ref(
+  [
+    {title: '姓名', key: 'babyName'},
+    {title: '行为记录', key: 'behavior'},
+    {title: '观察进阶', key: 'up', width: 240,child: [
+        {  title: 's1',   key: 's1' },
+        {  title: 's2',   key: 's2' },
+        {  title: 's3',   key: 's3' },
+        {  title: 's4',   key: 's4' },
+        {  title: 's5',   key: 's5' },
+        {  title: 's6',   key: 's6' },
+        {  title: 's7',   key: 's7' },
+        {  title: 's8',   key: 's8' }
+      ]
+    }
+  ]
+)
+
+
+const testColumns = ref(
+  [
+    {title: '领域', key: 'domainName', spanRow: true, width: 70},
+    {title: '能力', key: 'abilityName', width: 70 },
+    {  title: 's1',   key: 's1'},
+    {  title: 's2',   key: 's2'},
+    {  title: 's3',   key: 's3'},
+    {  title: 's4',   key: 's4'},
+    {  title: 's5',   key: 's5'},
+    {  title: 's6',   key: 's6'},
+    {  title: 's7',   key: 's7'},
+    {  title: 's8',   key: 's8'}
+  ]
+)
+
+const loading = ref(false)
+
+const htmlToPng = (id: string) => {
+  return new Promise((resolve) => {
+    html2canvas(document.querySelector(id)!).then(canvas => {
+      resolve(canvas.toDataURL(''))
+    });
+  })
+ }
+
+const exportReport = () =>  {
+  loading.value = true
+
+  html2canvas(document.querySelector('#cover')!).then(async canvas => {
+  
+    const cover =  canvas.toDataURL('')
+
+    const record = await htmlToPng('#capture')
+
+
+    const classRecord = await tableDom.value.getChartImg() 
+
+    console.log('reportScene:', Object.keys(reportSceneDom.value));
+
+    const reportSceneDomKeys = Object.keys(reportSceneDom.value)
+    const reportDomainDomKeys = Object.keys(reportDomainDom.value)
+    const gradeDomainDomKeys = Object.keys(gradeDomainDom.value).filter(key => key !=  'undefined')
+    
+    const _reportScene = []
+
+    let _reportDomain = []
+
+    const _gradeDomainDom = []
+    
+    for (let index = 0; index < Object.keys(reportSceneDom.value).length; index++) {
+      const key = reportSceneDomKeys[index]
+      if (reportSceneDom.value[key].el) {
+        // @ts-ignore
+        const img = await reportSceneDom.value[key].el.getChartImg()
+        
+        // @ts-ignore
+        _reportScene.push({  img: img,   title: reportSceneDom.value[key].title })
+      }
+    }
+
+    console.log('reportDomain.value:', reportDomainDom.value);
+
+    for (let index = 0; index < Object.keys(reportDomainDom.value).length; index++) {
+      const key = reportDomainDomKeys[index]
+      console.log('key:', key);
+
+      if (reportDomainDom.value[key].el) {
+         // @ts-ignore
+        const img =  await reportDomainDom.value[key].el.getChartImg()
+        console.log('img--over:', key);
+        // @ts-ignore
+        _reportDomain.push({  img: img,  title: reportDomainDom.value[key].title })
+      }
+     
+    }
+
+    console.log('gradeDomainDom.value:', gradeDomainDom.value);
+    
+
+    for (let index = 0; index < gradeDomainDomKeys.length; index++) {
+      const key = gradeDomainDomKeys[index]
+      console.log(' gradeDomainDom.value[key].el:',  key);
+      // @ts-ignore
+      if (gradeDomainDom.value[key].el) {
+        const img = gradeDomainDom.value[key] && await gradeDomainDom.value[key].el.getChartImg()
+        // @ts-ignore
+        _gradeDomainDom.push({  img: img,  title: gradeDomainDom.value[key].title })
+      }
+ 
+    }
+
+    const teacherRecordDomImg =  await teacherRecordDom.value.getChartImg()
+    const studentDomainDomImg =  await studentDomainDom.value.getChartImg()
+
+    console.log('reportDomain.value:', _gradeDomainDom);
+    
+
+    exportSmartCenter({
+      cover,
+      baseInfo: `${baseCount.value.schoolName}班级${baseCount.value.classCount}个, 共有学生${baseCount.value.behavior}名个 ,在本评估周期内共统计到教师的观察记录${baseCount.value.behavior} 条,观察记录到学生${baseCount.value.done.length}名,未观察到的学生${baseCount.value.notDone.length}名。`,
+      record,
+      classRecord,
+      _reportScene,
+      _reportDomain,
+      _gradeDomainDom,
+      teacherRecordDomImg,
+      studentDomainDomImg
+    }, async (progress: number) => {
+      const r = (progress * 100).toFixed(0) 
+      if (r === '100') {
+        loading.value = false
+      }
+    })
+
+    
+  })
+
+  
+
+}
+
+onMounted(async () => {
+  await getAppToken()
+  // @ts-ignore
+  const $par = JSON.parse(decodeURIComponent(atob(p!)))
+
+  // @ts-ignore
+  // type.value = typeAs === 1 ? 'school' : 'class'
+
+  
+  getCenterBaseCount( $par)
+  getCenteClassRecord( $par)
+  getCenteReportDmain( $par)
+  getCenteReportScene( $par)
+  getCenteStudentDomain( $par)
+  getCenteTeacherRecord( $par)
+  getCenteGradeDomain( $par)
+})
+</script>
+
+
+<style scoped lang="scss">
+
+$font-size-H1: 26px;
+$font-size-H2: 20px;
+$font-size-H3: 18px;
+$font-size-H4: 16px;
+$font-size-H5: 14px;
+
+.smart-center {
+  padding-bottom: 160px;
+  .smart-center-cover {
+    width: 100vw;
+    height: 667px;
+    background-size: contain;
+    position: relative;
+    .smart-center-name {
+      position: absolute;
+      left: 20px;
+      top: 240px;
+      font-size: 18px;
+    }
+    .smart-center-date {
+      position: absolute;
+      left: 20px;
+      top: 270px;
+      display: flex;
+      font-size: 14px;
+      .smart-center-date-title {
+        margin-right: 6px;
+      }
+    }
+  }
+  .smart-center-analysis {
+    // margin-top: 20px;
+    padding: 20px;
+    box-sizing: border-box;
+   .smart-center-analysis-title {
+    font-size: $font-size-H1;
+    margin-bottom: 16px;
+   }
+   .smart-center-analysis-item-1 {
+      .smart-center-analysis-item-1-title {
+        font-size: $font-size-H2;
+      }
+      .smart-center-analysis-item-1-content {
+        font-size: $font-size-H4;
+      }
+    }
+    .smart-center-analysis-item-2 {
+      margin-top: 20px;
+      .smart-center-analysis-item-1-title {
+        font-size: $font-size-H2;
+      }
+      .smart-center-analysis-item-1-subtitle {
+        font-size: $font-size-H3;
+      }
+    
+    }
+    .smart-center-analysis-item-2-content {
+        .base-table {
+          margin-top: 12px;
+          .base-table-item {
+            width: 100%;
+            height: 40px;
+            display: flex;
+            background-color: rgb(113, 207, 224);
+            font-size: 12px;
+            color: #fff;
+            .title {
+              width: 50%;
+              display: flex;
+              justify-content: center;
+              align-items: center;
+              border-bottom: 1px solid #fff;
+              border-right: 1px solid #fff;
+            }
+            .value {
+              width: 50%;
+              display: flex;
+              justify-content: center;
+              align-items: center;
+              border-bottom: 1px solid #fff;
+            }
+          }
+        }
+      }
+    .smart-center-analysis-item-3 {
+      .smart-center-analysis-item-3-subtitle {
+        margin-top: 20px;
+        font-size: $font-size-H4;
+        margin-bottom: 20px;
+      }
+    }
+  }
+
+  .title-1 {
+    font-size: $font-size-H1;
+  }
+
+  .title-2 {
+    font-size: $font-size-H2;
+    margin: 24px 0;
+  }
+
+  .title-3 {
+    font-size: $font-size-H3;
+  }
+
+  .title-4 {
+    font-size: $font-size-H4;
+    margin: 12px 0;
+  }
+
+  .title-5 {
+    font-size: $font-size-H5;
+  }
+
+  .picture-title {
+    font-size: $font-size-H5;
+    margin-top: 20px;
+    margin-bottom: 10px;
+    text-align: center;
+    color: #5c5b5b;
+  }
+}
+
+
+</style>

+ 15 - 2
vite.config.ts

@@ -1,8 +1,8 @@
-import { defineConfig } from "vite";
 import vue from "@vitejs/plugin-vue";
 import { resolve } from "path";
+import { defineConfig } from "vite";
 
-import legacy from '@vitejs/plugin-legacy'
+import legacy from '@vitejs/plugin-legacy';
 import { terser } from 'rollup-plugin-terser';
 // https://vitejs.dev/config/
 
@@ -93,6 +93,19 @@ export default defineConfig({
     // 使用路径别名时想要省略的后缀名,可以自己 增减
     extensions: [".js", ".json", ".ts", ".vue"],
   },
+  esbuild: {
+    tsconfigRaw: false
+  },
+  build:{
+    rollupOptions: {
+      onwarn(warning, warn) {
+        // 忽略所有警告
+        return
+      }
+    },
+    reportCompressedSize: false,
+    chunkSizeWarningLimit: Infinity,
+  }
   // build: {
   //   target: 'esnext', // 输出 ESNext 代码
   //   minify: false, // 禁用代码压缩

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio