Explorar el Código

学生端增加学生报告册导出pdf

liurongli hace 5 meses
padre
commit
e8a8f8b8cf
Se han modificado 31 ficheros con 7856 adiciones y 161 borrados
  1. 267 14
      package-lock.json
  2. 4 0
      package.json
  3. 3 3
      src/App.vue
  4. BIN
      src/assets/bg/student_report_cover.webp
  5. BIN
      src/assets/icon/close.webp
  6. BIN
      src/assets/report/header_left_student.webp
  7. BIN
      src/assets/report/header_right_student.webp
  8. BIN
      src/assets/report/score_dimidiate_icon.webp
  9. BIN
      src/assets/report/score_no_icon.webp
  10. BIN
      src/assets/report/score_yes_icon.webp
  11. BIN
      src/assets/report/turn_next.webp
  12. BIN
      src/assets/report/turn_prev.webp
  13. 40 7
      src/components/PaperImage.vue
  14. 7 0
      src/router/index.js
  15. 949 2
      src/styles/report.scss
  16. 3352 0
      src/utils/turn.js
  17. 130 80
      src/views/analysisReport/components/dCharts/barChart.vue
  18. 36 12
      src/views/analysisReport/components/dCharts/differenceChart.vue
  19. 46 22
      src/views/analysisReport/components/dCharts/lineChart.vue
  20. 66 14
      src/views/analysisReport/components/dCharts/radarCharts.vue
  21. 124 0
      src/views/analysisReport/studentPage/downloadPdf/components/bookCover.vue
  22. 753 0
      src/views/analysisReport/studentPage/downloadPdf/components/bookFlip.vue
  23. 262 0
      src/views/analysisReport/studentPage/downloadPdf/components/css/magazine.css
  24. BIN
      src/views/analysisReport/studentPage/downloadPdf/components/image/loader.gif
  25. 325 0
      src/views/analysisReport/studentPage/downloadPdf/components/js/magazine.js
  26. 26 0
      src/views/analysisReport/studentPage/downloadPdf/components/js/zoom.min.js
  27. 1432 0
      src/views/analysisReport/studentPage/downloadPdf/studentReport.vue
  28. 11 2
      src/views/analysisReport/studentPage/mainPage.vue
  29. 15 4
      src/views/analysisReport/studentPage/scrolReport/transcript.vue
  30. 1 1
      src/views/analysisReport/studentPage/scrolReport/transcript_single.vue
  31. 7 0
      vue.config.js

+ 267 - 14
package-lock.json

@@ -17,13 +17,17 @@
         "core-js": "^3.38.1",
         "crypto-js": "^4.2.0",
         "dayjs": "^1.11.13",
+        "dom-to-image": "^2.6.0",
         "echarts": "^5.5.1",
         "element-ui": "^2.15.14",
         "font-awesome": "^4.7.0",
         "html-webpack-plugin": "^5.6.0",
+        "html2canvas": "^1.4.1",
+        "jquery": "^3.5.1",
         "js-cookie": "^3.0.5",
         "js-md5": "^0.8.3",
         "jsencrypt": "^3.3.2",
+        "jspdf": "^2.5.2",
         "kde": "^0.0.1",
         "lottie-web": "^5.12.2",
         "nprogress": "^0.2.0",
@@ -1642,7 +1646,6 @@
       "version": "7.28.4",
       "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
       "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
-      "dev": true,
       "engines": {
         "node": ">=6.9.0"
       }
@@ -2705,6 +2708,12 @@
       "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
       "dev": true
     },
+    "node_modules/@types/raf": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
+      "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
+      "optional": true
+    },
     "node_modules/@types/range-parser": {
       "version": "1.2.7",
       "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
@@ -3935,7 +3944,6 @@
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
       "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
-      "dev": true,
       "bin": {
         "atob": "bin/atob.js"
       },
@@ -4367,6 +4375,17 @@
         "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
       }
     },
+    "node_modules/btoa": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
+      "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
+      "bin": {
+        "btoa": "bin/btoa.js"
+      },
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
     "node_modules/buffer": {
       "version": "5.7.1",
       "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -4515,6 +4534,31 @@
         "element-size": "^1.1.1"
       }
     },
+    "node_modules/canvg": {
+      "version": "3.0.11",
+      "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
+      "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
+      "optional": true,
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "@types/raf": "^3.4.0",
+        "core-js": "^3.8.3",
+        "raf": "^3.4.1",
+        "regenerator-runtime": "^0.13.7",
+        "rgbcolor": "^1.0.1",
+        "stackblur-canvas": "^2.0.0",
+        "svg-pathdata": "^6.0.3"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/canvg/node_modules/regenerator-runtime": {
+      "version": "0.13.11",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+      "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+      "optional": true
+    },
     "node_modules/case-sensitive-paths-webpack-plugin": {
       "version": "2.4.0",
       "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz",
@@ -5258,6 +5302,14 @@
       "resolved": "https://registry.npmjs.org/css-global-keywords/-/css-global-keywords-1.0.1.tgz",
       "integrity": "sha512-X1xgQhkZ9n94WDwntqst5D/FKkmiU0GlJSFZSV3kLvyJ1WC5VeyoXDOuleUD+SIuH9C7W05is++0Woh0CGfKjQ=="
     },
+    "node_modules/css-line-break": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
     "node_modules/css-loader": {
       "version": "6.11.0",
       "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz",
@@ -6014,6 +6066,11 @@
         "url": "https://github.com/fb55/entities?sponsor=1"
       }
     },
+    "node_modules/dom-to-image": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/dom-to-image/-/dom-to-image-2.6.0.tgz",
+      "integrity": "sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA=="
+    },
     "node_modules/dom-walk": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
@@ -6044,6 +6101,12 @@
         "url": "https://github.com/fb55/domhandler?sponsor=1"
       }
     },
+    "node_modules/dompurify": {
+      "version": "2.5.8",
+      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz",
+      "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
+      "optional": true
+    },
     "node_modules/domutils": {
       "version": "2.8.0",
       "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
@@ -7235,6 +7298,11 @@
         "node": ">=0.8.0"
       }
     },
+    "node_modules/fflate": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+      "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
+    },
     "node_modules/figures": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@@ -8749,6 +8817,18 @@
         }
       }
     },
+    "node_modules/html2canvas": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+      "dependencies": {
+        "css-line-break": "^2.1.0",
+        "text-segmentation": "^1.0.3"
+      },
+      "engines": {
+        "node": ">=8.0.0"
+      }
+    },
     "node_modules/htmlparser2": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
@@ -9404,10 +9484,9 @@
       }
     },
     "node_modules/jquery": {
-      "version": "3.7.1",
-      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
-      "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
-      "peer": true
+      "version": "3.5.1",
+      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
+      "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
     },
     "node_modules/js-cookie": {
       "version": "3.0.5",
@@ -9530,6 +9609,23 @@
         "graceful-fs": "^4.1.6"
       }
     },
+    "node_modules/jspdf": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
+      "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
+      "dependencies": {
+        "@babel/runtime": "^7.23.2",
+        "atob": "^2.1.2",
+        "btoa": "^1.2.1",
+        "fflate": "^0.8.1"
+      },
+      "optionalDependencies": {
+        "canvg": "^3.0.6",
+        "core-js": "^3.6.0",
+        "dompurify": "^2.5.4",
+        "html2canvas": "^1.0.0-rc.5"
+      }
+    },
     "node_modules/kdbush": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
@@ -12895,6 +12991,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/rgbcolor": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+      "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+      "optional": true,
+      "engines": {
+        "node": ">= 0.8.15"
+      }
+    },
     "node_modules/right-now": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/right-now/-/right-now-1.0.0.tgz",
@@ -13795,6 +13900,15 @@
         "node": "*"
       }
     },
+    "node_modules/stackblur-canvas": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+      "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+      "optional": true,
+      "engines": {
+        "node": ">=0.1.14"
+      }
+    },
     "node_modules/stackframe": {
       "version": "1.3.4",
       "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
@@ -14059,6 +14173,15 @@
         "svg-path-bounds": "^1.0.1"
       }
     },
+    "node_modules/svg-pathdata": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+      "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+      "optional": true,
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
     "node_modules/svg-tags": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz",
@@ -14257,6 +14380,14 @@
         "vectorize-text": "^3.2.1"
       }
     },
+    "node_modules/text-segmentation": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
     "node_modules/text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -14729,6 +14860,14 @@
         "node": ">= 0.4.0"
       }
     },
+    "node_modules/utrie": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+      "dependencies": {
+        "base64-arraybuffer": "^1.0.2"
+      }
+    },
     "node_modules/uuid": {
       "version": "8.3.2",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
@@ -17279,8 +17418,7 @@
     "@babel/runtime": {
       "version": "7.28.4",
       "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
-      "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
-      "dev": true
+      "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="
     },
     "@babel/template": {
       "version": "7.27.2",
@@ -18090,6 +18228,12 @@
       "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
       "dev": true
     },
+    "@types/raf": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
+      "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
+      "optional": true
+    },
     "@types/range-parser": {
       "version": "1.2.7",
       "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
@@ -19083,8 +19227,7 @@
     "atob": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
-      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
-      "dev": true
+      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
     },
     "atob-lite": {
       "version": "1.0.0",
@@ -19410,6 +19553,11 @@
         "update-browserslist-db": "^1.1.3"
       }
     },
+    "btoa": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
+      "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g=="
+    },
     "buffer": {
       "version": "5.7.1",
       "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -19506,6 +19654,30 @@
         "element-size": "^1.1.1"
       }
     },
+    "canvg": {
+      "version": "3.0.11",
+      "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
+      "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime": "^7.12.5",
+        "@types/raf": "^3.4.0",
+        "core-js": "^3.8.3",
+        "raf": "^3.4.1",
+        "regenerator-runtime": "^0.13.7",
+        "rgbcolor": "^1.0.1",
+        "stackblur-canvas": "^2.0.0",
+        "svg-pathdata": "^6.0.3"
+      },
+      "dependencies": {
+        "regenerator-runtime": {
+          "version": "0.13.11",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+          "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+          "optional": true
+        }
+      }
+    },
     "case-sensitive-paths-webpack-plugin": {
       "version": "2.4.0",
       "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz",
@@ -20140,6 +20312,14 @@
       "resolved": "https://registry.npmjs.org/css-global-keywords/-/css-global-keywords-1.0.1.tgz",
       "integrity": "sha512-X1xgQhkZ9n94WDwntqst5D/FKkmiU0GlJSFZSV3kLvyJ1WC5VeyoXDOuleUD+SIuH9C7W05is++0Woh0CGfKjQ=="
     },
+    "css-line-break": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+      "requires": {
+        "utrie": "^1.0.2"
+      }
+    },
     "css-loader": {
       "version": "6.11.0",
       "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz",
@@ -20695,6 +20875,11 @@
         }
       }
     },
+    "dom-to-image": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/dom-to-image/-/dom-to-image-2.6.0.tgz",
+      "integrity": "sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA=="
+    },
     "dom-walk": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
@@ -20713,6 +20898,12 @@
         "domelementtype": "^2.2.0"
       }
     },
+    "dompurify": {
+      "version": "2.5.8",
+      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz",
+      "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
+      "optional": true
+    },
     "domutils": {
       "version": "2.8.0",
       "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
@@ -21654,6 +21845,11 @@
         "websocket-driver": ">=0.5.1"
       }
     },
+    "fflate": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+      "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
+    },
     "figures": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@@ -22961,6 +23157,15 @@
         "tapable": "^2.0.0"
       }
     },
+    "html2canvas": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+      "requires": {
+        "css-line-break": "^2.1.0",
+        "text-segmentation": "^1.0.3"
+      }
+    },
     "htmlparser2": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
@@ -23422,10 +23627,9 @@
       }
     },
     "jquery": {
-      "version": "3.7.1",
-      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
-      "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
-      "peer": true
+      "version": "3.5.1",
+      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
+      "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
     },
     "js-cookie": {
       "version": "3.0.5",
@@ -23525,6 +23729,21 @@
         "universalify": "^2.0.0"
       }
     },
+    "jspdf": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
+      "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
+      "requires": {
+        "@babel/runtime": "^7.23.2",
+        "atob": "^2.1.2",
+        "btoa": "^1.2.1",
+        "canvg": "^3.0.6",
+        "core-js": "^3.6.0",
+        "dompurify": "^2.5.4",
+        "fflate": "^0.8.1",
+        "html2canvas": "^1.0.0-rc.5"
+      }
+    },
     "kdbush": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
@@ -26141,6 +26360,12 @@
       "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
       "dev": true
     },
+    "rgbcolor": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+      "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+      "optional": true
+    },
     "right-now": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/right-now/-/right-now-1.0.0.tgz",
@@ -26857,6 +27082,12 @@
       "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz",
       "integrity": "sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ=="
     },
+    "stackblur-canvas": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+      "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+      "optional": true
+    },
     "stackframe": {
       "version": "1.3.4",
       "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
@@ -27079,6 +27310,12 @@
         "svg-path-bounds": "^1.0.1"
       }
     },
+    "svg-pathdata": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+      "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+      "optional": true
+    },
     "svg-tags": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz",
@@ -27221,6 +27458,14 @@
         "vectorize-text": "^3.2.1"
       }
     },
+    "text-segmentation": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+      "requires": {
+        "utrie": "^1.0.2"
+      }
+    },
     "text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -27605,6 +27850,14 @@
       "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
       "dev": true
     },
+    "utrie": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+      "requires": {
+        "base64-arraybuffer": "^1.0.2"
+      }
+    },
     "uuid": {
       "version": "8.3.2",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",

+ 4 - 0
package.json

@@ -20,13 +20,17 @@
     "core-js": "^3.38.1",
     "crypto-js": "^4.2.0",
     "dayjs": "^1.11.13",
+    "dom-to-image": "^2.6.0",
     "echarts": "^5.5.1",
     "element-ui": "^2.15.14",
     "font-awesome": "^4.7.0",
     "html-webpack-plugin": "^5.6.0",
+    "html2canvas": "^1.4.1",
+    "jquery": "^3.5.1",
     "js-cookie": "^3.0.5",
     "js-md5": "^0.8.3",
     "jsencrypt": "^3.3.2",
+    "jspdf": "^2.5.2",
     "kde": "^0.0.1",
     "lottie-web": "^5.12.2",
     "nprogress": "^0.2.0",

+ 3 - 3
src/App.vue

@@ -13,13 +13,13 @@ export default {
     mounted() {
         // INFO: 本地调试,自动登录
         if (process.env.NODE_ENV === 'development') {
-            // this.SubmitLogin();
+            this.SubmitLogin();
         }
     },
     methods: {
         SubmitLogin() {
-            const username = '99689820198805';
-            const password = '123456';
+            const username = '2514875210230104';
+            const password = '251487@5210230104';
             // const type = '1';
             const schoolType = sessionStorage.getItem('schoolType');
             if(getToken() && schoolType){

BIN
src/assets/bg/student_report_cover.webp


BIN
src/assets/icon/close.webp


BIN
src/assets/report/header_left_student.webp


BIN
src/assets/report/header_right_student.webp


BIN
src/assets/report/score_dimidiate_icon.webp


BIN
src/assets/report/score_no_icon.webp


BIN
src/assets/report/score_yes_icon.webp


BIN
src/assets/report/turn_next.webp


BIN
src/assets/report/turn_prev.webp


+ 40 - 7
src/components/PaperImage.vue

@@ -1,8 +1,8 @@
 <template>
 
   <div class="paper_container"  ref="paperContainer"  @mousedown="onMouseDown" @mousemove="onMouseMove" @mouseup="onMouseUp"  @wheel="onWheel">
-      <canvas id="paperCanvas" ref="paperCanvas" class="paper_canvas" @mouseleave="onCanvasLeave"></canvas>
-      <div :id="`imgContainer${imageIndex}`" class="img_container">
+      <canvas id="paperCanvas" ref="paperCanvas" class="paper_canvas" @mouseleave="onCanvasLeave" :style="{transform: `rotate(${rotateDeg}deg)`}"></canvas>
+      <div ref="imgContainer" :id="`imgContainer${imageIndex}`" class="img_container" :style="{transform: `rotate(${rotateDeg}deg)`}">
         <img v-if="paperImgUrl" :src="paperImgUrl" >
       </div>
       <div class="no_paper_url" v-if="paperImgUrl==''">
@@ -50,6 +50,20 @@ export default {
       type:Boolean,
       default:true
     },//是否可以拖动 默认可以拖动
+    isWheel:{
+      type:Boolean,
+      default:true
+    },//是否可以滚动放大、缩小 默认可以滚动
+
+    isShowContextMenu:{
+      type:Boolean,
+      default:true
+    },//是否显示右键菜单
+
+    rotateDeg:{
+      type:String,
+      default:'0'
+    },//答题卡方向
 
     isAbnormal:{
       type:Boolean,
@@ -263,6 +277,7 @@ export default {
       // 处理右键点击事件
       handleRightClick(event) 
       {
+        if(!this.isShowContextMenu) return false
         event.preventDefault();
         event.stopPropagation(); // 阻止事件继续向上冒泡
         // 设置菜单位置(相对于 paper_container 的位置)
@@ -1096,11 +1111,17 @@ export default {
     ImageInfoChange(){
       let {width,height} = this.paperImgInfo;
       // console.log("打印纸张试卷的图片宽高",this.paperImgInfo);
-      let imgDom = document.getElementById(`imgContainer${this.imageIndex}`);
+      // let imgDom = document.getElementById(`imgContainer${this.imageIndex}`);
+      // imgDom.style.width = width * this.zoomRate*this.scale + 'px';
+      // imgDom.style.height = height * this.zoomRate*this.scale + 'px';
+      // imgDom.style.left = this.position.x + 'px';
+      // imgDom.style.top = this.position.y + 'px';
+
+      let imgDom = this.$refs.imgContainer;
       imgDom.style.width = width * this.zoomRate*this.scale + 'px';
       imgDom.style.height = height * this.zoomRate*this.scale + 'px';
       imgDom.style.left = this.position.x + 'px';
-      imgDom.style.top = this.position.y + 'px';
+      imgDom.style.top = this.position.y + 'px';  
     },
 
     //图片加载完成后绘制图片
@@ -1692,8 +1713,15 @@ export default {
     updateZoomAndPaperInfo() {
       // this.zoomRate = this.containerHeight / this.paperImgInfo.height;//
       // 计算基于宽度和高度的缩放率
-      const widthZoomRate = this.containerWidth / this.paperImgInfo.width;
-      const heightZoomRate = this.containerHeight / this.paperImgInfo.height;
+      let widthZoomRate = 1;
+      let heightZoomRate = 1;
+      if(this.rotateDeg=='-90'){//旋转90度 学生报告册使用
+        widthZoomRate = this.containerWidth / this.paperImgInfo.height;
+        heightZoomRate = (this.containerHeight - 40) / this.paperImgInfo.width;//减掉报告册底边距40px
+      }else{
+        widthZoomRate = this.containerWidth / this.paperImgInfo.width;
+        heightZoomRate = this.containerHeight / this.paperImgInfo.height;
+      }
       
       // 选择较小的缩放率作为基准,确保图像完整显示在容器内
       this.zoomRate = Math.min(widthZoomRate, heightZoomRate);
@@ -1717,7 +1745,11 @@ export default {
       console.log("开始居中画布",this.containerWidth,this.containerHeight);
       console.log("画布尺寸",this.canvasInfo.width,this.canvasInfo.height);
       this.position.x = (this.containerWidth - this.canvasInfo.width) / 2;
-      this.position.y = (this.containerHeight - this.canvasInfo.height) / 2;
+      if(this.rotateDeg=='-90'){//学生报告册使用
+        this.position.y = (this.containerHeight - this.canvasInfo.height) / 2 - 40;
+      }else{
+        this.position.y = (this.containerHeight - this.canvasInfo.height) / 2;
+      }
       this.$refs.paperCanvas.style.left = `${this.position.x}px`;
       this.$refs.paperCanvas.style.top = `${this.position.y}px`;
       console.log("中心化画布打印画布位置",this.position.x,this.position.y);
@@ -1811,6 +1843,7 @@ export default {
 
     // 鼠标滚轮事件
     onWheel(event) {
+      if(!this.isWheel) return false
       event.preventDefault();
       // 计算新的缩放比例
       const delta = event.deltaY < 0 ? 1 : -1;

+ 7 - 0
src/router/index.js

@@ -74,6 +74,13 @@ let studentAnalysisReport = {
                         title: "学生分析",
                     },
                 },
+                {
+                    path: "studentReport",
+                    meta: {
+                        title: "学生报告册",
+                    },
+                    component: () => import("@/views/analysisReport/studentPage/downloadPdf/studentReport"),
+                },
                 {
                     path: "personalWrongQuestions",
                     component: () => import("@/views/analysisReport/wrongQuestion/index"),

+ 949 - 2
src/styles/report.scss

@@ -1915,7 +1915,952 @@
 
   }
 }
+// 联考报告打印
+.joint_print_area
+{
+  font-family: Source Han Sans CN, Source Han Sans CN;
+  // width: calc(100% - 220px);
+  width: 100%;
+  height: 100%;
+  text-align: center;
+  .area_page
+  {
+    width:908px;
+    // width:520px;
+    // margin: auto;
+    height:1285px;
+    // height: 760px;
+    padding: 0 !important;
+    background-color: #fff;
+    // padding:32px;
+    box-shadow: 8px 8px 8px rgba(155, 155, 155, 0.1);
+    // margin-bottom: 20px;
+    box-sizing: border-box; // 设置盒模型为 border-box  使内部div100%的高度自动减去边距和边框
+    position: relative;
+    overflow: hidden;
+    //报告的封面内部样式
+    .report_cover {
+      width: 100%;
+      height: 100%;
+      box-sizing: border-box; // 确保内部 div 也使用 border-box
+      position: relative;
+      .cover_header {
+        width: 100%;
+        position: absolute;
+        top: 19%;
+        color: #000;
+        text-align: center;
+        .cover_header_title1 {
+          font-weight: bold;
+          font-size: 36px;
+          color: #000000;
+          line-height: 1.5;
+          padding: 0 0.625rem;
+        }
+        .cover_header_title2 {
+          font-weight: 400;
+          font-size: 28px;
+          color: #000000;
+          margin-top: 1.75rem;
+        }
+      }
+      .full_screen_button{
+        position: absolute;
+        width: 100%;
+        top: 75%;
+        display: flex;
+        justify-content: center;
+        left: 50%;
+        transform: translateX(-50%);
+        .el-button{
+          cursor: pointer;
+          width: 10rem;
+          height: 2.75rem;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          background: rgba(0,0,0,0.5);
+          box-shadow: 0px 8px 8px 0px rgba(0,0,0,0.25);
+          border-radius: 70px;
+          // border: 4px solid rgba(255,255,255,0.1);
+          font-weight: 500;
+          font-size: 1rem;
+          color: rgba(255,255,255,0.8);
+          padding: 0;
+        }
+        .el-button.is-disabled{
+          cursor: not-allowed;
+          background: rgba(0,0,0,0.3);
+        }
+        .el-button:hover{
+          border-color: transparent;
+        }
+        .el-button:focus{
+          border-color: transparent;
+        }
+      }
+      .cover_footer {
+        position: absolute;
+        width: 100%;
+        top: 87%;
+        color: #333;
+        text-align: center;
+        .cover_footer_desc {
+          font-weight: 400;
+          font-size: 16px;
+          color: #333333;
+          line-height: 1.4;
+        }
+      }
+    }
+    .area_title
+    {
+      font-weight: bold;
+      font-size: 1.3rem;
+      color: #000000;
+      line-height: 1.4;
+      text-align: center;
+      padding-bottom: 1.3rem;
+    }
+
+    .area_module
+    {
+      width: 100%;
+      height: auto;
+      .area_module_title
+      {
+        font-weight: 500;
+        font-size: 0.8125rem;
+        color: #000000;
+        padding-bottom: 0.625rem;
+        line-height: 1.25rem;
+        text-align: left;
+      }
+      .area_module_describe
+      {
+        font-weight: 400;
+        font-size: 0.69rem;
+        color: #666666;
+        line-height: 1.2rem;
+        text-align: left;
+        padding-bottom: 1.3rem;
+        border-bottom: 1px solid #F3F3F3;
+        .max_color {
+            color: #3BA272;
+        }
+        .min_color {
+            color: #EE6666;
+        }
+      }
+      .area_module_table
+      {
+        &.onLine{
+          .el-table--group, .el-table--border{
+            border: 1px solid #CED8EE;
+          }
+        }
+        width: 100% !important; // 确保表格宽度为 100%
+        // 添加 overflow 属性以处理内容溢出
+        overflow: auto;
+        .el-table--group, .el-table--border{
+          border: 1px solid #CED8EE;
+          border-right: none;
+          border-bottom: none;
+          font-size: 0.6875rem;
+          color: #000000;
+        }
+        .el-table::before, .el-table--group::after, .el-table--border::after{
+          background-color:#CED8EE;
+        }
+        .el-table
+        {
+          width: 100%; 
+          border-collapse: collapse;
+          border-radius: 8px;
+          .cell{
+            padding-left: 0.3rem;
+            padding-right: 0.3rem;
+            line-height: 1.6875rem;
+            &.el-tooltip{
+              min-width:3.125rem;
+            }
+          }
+          // 设置表头样式
+          th {
+              background-color: #F5F7FA; /* 背景颜色 */
+              color: #000000; /* 文字颜色 */
+              padding:0px; /* 内边距 */
+              font-weight: 500;
+              font-size: 0.6875rem;
+              line-height: 100%;
+              letter-spacing: 0;
+              text-align: center;
+              border-bottom: 1px solid #CED8EE; /* 底部边框 */
+              border-right: 1px solid #CED8EE;
+              height: 1.6875rem;
+              position: relative;
+              .header_row{
+                display: block;
+                line-height: 1.4;
+              }
+          }
+          /* 设置表格内容样式 */
+          td {
+              padding: 0px; /* 内边距 */
+              text-align: center; /* 文字对齐方式 */
+              border-bottom:  1px solid #CED8EE; /* 底部边框 */
+              border-right:  1px solid #CED8EE; /* 底部边框 */
+              height: 1.6875rem;
+          }
+          /* 设置表格行的悬停效果 */
+          tr:hover {
+            background-color: #f5f5f5; /* 悬停时的背景颜色 */
+          }
+      
+          /* 设置表格的斑马线效果 */
+          tr:nth-child(even) {
+              background-color: #FAFAFA; /* 偶数行的背景颜色 */
+          }
+        }
+        // 二级表头样式
+        thead tr:nth-child(2) th.table_cell {
+          background-color: #f4f6f8;
+          color: #333333;
+          font-weight: 600;
+          font-size: 14px;
+          height: 40px;
+          text-align: center;
+        }
+        .title_blue
+        {
+          width: 52px;
+          height: 25px;
+          margin: auto;
+          border-radius: 42px;
+          text-align: center;
+          line-height: 25px;
+          font-weight: 400;
+          font-size: 12px;
+          letter-spacing: 0;
+          text-align: center;
+          background-color: #5470C6;
+          color:#fff;
+        }
+
+        .title_green
+        {
+          width: 52px;
+          height: 25px;
+          margin: auto;
+          border-radius: 42px;
+          text-align: center;
+          line-height: 25px;
+          font-weight: 400;
+          font-size: 12px;
+          letter-spacing: 0;
+          text-align: center;
+          background-color: #3BA272;
+          color:#fff;
+        }
+      }
+
+      .area_module_chart
+      {
+        width: 100%;
+        height: auto;
+        &.flex{
+          display: flex;
+          justify-content: space-between;
+        }
+        .echart_content
+        {
+          width: 100% !important;
+          min-height: 300px !important;
+        }
+        .module_chart_left{
+          width: 52%;
+        }
+        .module_chart_right{
+          width: 45%;
+        }
+      }
+    }
+    .area_header
+    {
+      width: 100%;
+      margin: auto;
+      height: 2.7rem;
+      background: rgba(238,102,102,0.1);
+      border-radius: 8px;
+      border:1px solid rgba(245,108,108,0.5);
+      font-weight: 600;
+      font-size: 1rem;
+      color: #EE6666;
+      text-align: center;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      &.bg_blue{
+        background: rgba(84,112,198,0.1);
+        border: 1px solid rgba(84,112,198,0.5);
+        color: #5470C6;
+      }
+      .header_icon_left
+      {
+        width: 1.4rem;
+        height: 1.4rem;
+        margin-right: 1.6rem;
+      }
+
+      .header_icon_right
+      {
+        width: 1.4rem;
+        height: 1.4rem;
+        margin-left: 1.6rem;
+      }
+      
+    }
+    //考试总览数据
+    .area_info
+    {
+      width: 100%;
+      height: 4.2rem;
+      display: flex;
+      justify-content: space-between;
+      .info_item
+      {
+        width: calc(33.3333% - 8px);
+        border-radius: 4px;
+        display: flex;
+        align-items: center;
+        padding: 0.2rem;
+        box-sizing: border-box;
+        .item_number
+        {
+          // width: 25%;
+          height: auto;
+          text-align: center;
+          overflow: hidden;
+          white-space: nowrap;
+
+          .number_title
+          {
+            font-weight: 400;
+            font-size: 0.75rem;
+            transform: scale(0.75); /* 缩放为原来的50%(16px × 0.5 = 视觉8px) */
+            transform-origin: left top; /* 确保缩放原点在左上角,避免位置偏移 */
+            // color: #15325E;
+          }
+          .number_value
+          { 
+            font-weight: 800;
+            font-size: 0.8125rem;
+            // color: #15325E;
+            margin-top: 0.5rem;
+            transform: scale(0.75); /* 缩放为原来的50%(16px × 0.5 = 视觉8px) */
+            transform-origin: left top; /* 确保缩放原点在左上角,避免位置偏移 */
+          }
+        }
+      }
+      .bg_0
+      {
+        background: linear-gradient( 135deg, #B4D2FF 0%, #F1F7FF 100%);
+        color:#15325E;
+      }
+      .bg_1
+      {
+        background: linear-gradient( 135deg, #FFCAE1 0%, #FFF3F8 100%);
+        color:#5D1638;
+      }
+      .bg_2
+      {
+        background: linear-gradient( 135deg, #D2C6FF 0%, #F7F1FF 100%);
+        color:#41155E;
+      }
+    }
+    //命题分析数据样式
+    .analysis_info
+    {
+      width: 100%;
+      display: flex;
+      justify-content: space-between;
+      .info_item
+      {
+        width: calc((100% - 40px)/5);
+        border-radius: 4px;
+        display: flex;
+        align-items: center;
+        .item_number
+        {
+          width: calc(100% - 20px);
+          margin: auto;
+          height: auto;
+          text-align: left;
+          padding: 20px;
+          .number_title
+          {
+            font-weight: 400;
+            font-size: 12px;
+            
+            line-height: 20px;
+          }
+          .number_value
+          { 
+            font-weight: bold;
+            font-size: 18px;
+          
+            line-height:30px;
+          }
+        }
+      }
+
+      .bg_0
+      {
+        background: linear-gradient( 135deg, #B4D2FF 0%, #F1F7FF 100%);
+        color:#15325E;
+      }
+
+      .bg_1
+      {
+        background: linear-gradient( 135deg, #FFCAE1 0%, #FFF3F8 100%);
+        color:#5D1638;
+      }
+
+      .bg_2
+      {
+        background: linear-gradient( 135deg, #D2C6FF 0%, #F7F1FF 100%);
+        color:#41155E;
+      }
+
+      .bg_3
+      {
+        background: linear-gradient( 135deg, #FFCAE1 0%, #FFF3F8 100%);
+        color:#5D1638;
+      }
+
+      .bg_4
+      {
+        background: linear-gradient( 135deg, #D2C6FF 0%, #F7F1FF 100%);
+        color:#41155E;
+      }
+    }
+
+    .area_line
+    {
+      width: 100%;
+      height: 1px;
+      border-bottom: 1px solid #E9E9E9;
+    }
+
+    .area_page_number
+    {
+      position: absolute;
+      width: 100%;
+      line-height: 15px;
+      bottom: 15px;
+      left: 0;
+      text-align: center;
+      color: #333333;
+      font-size: 12px;
+      font-weight: 400;
+    }
+
+    .area_page_title
+    {
+      position: absolute;
+      width: 100%;
+      top: 16px;
+      right: 40px;
+      text-align: right;
+      color: #333333;
+      font-size: 12px;
+      font-weight: 400;
+    }
+
+  }
+}
+// 联考报告打印  网页模式
+.joint_print_area
+{
+  font-family: Source Han Sans CN, Source Han Sans CN;
+  // width: calc(100% - 220px);
+  width: 100%;
+  height: 100%;
+  text-align: center;
+  .web_mode{//网页模式
+    width: 908px;
+    height: 1285px;
+    margin: 0 auto;
+    .area_page
+    {
+      width:908px;
+      // width:520px;
+      // margin: auto;
+      height:1285px;
+      // height: 760px;
+      margin-top: 20px;
+      padding: 40px 40px 0!important;
+      background-color: #fff;
+      // padding:32px;
+      box-shadow: 8px 8px 8px rgba(155, 155, 155, 0.1);
+      // margin-bottom: 20px;
+      box-sizing: border-box; // 设置盒模型为 border-box  使内部div100%的高度自动减去边距和边框
+      position: relative;
+      overflow: hidden;
+      //报告的封面内部样式
+      .report_cover {
+        width: 100%;
+        height: 100%;
+        box-sizing: border-box; // 确保内部 div 也使用 border-box
+        position: relative;
+        .cover_header {
+          width: 100%;
+          position: absolute;
+          top: 19%;
+          color: #000;
+          text-align: center;
+          .cover_header_title1 {
+            font-weight: bold;
+            font-size: 36px;
+            color: #000000;
+            line-height: 1.5;
+            padding: 0 0.625rem;
+          }
+          .cover_header_title2 {
+            font-weight: 400;
+            font-size: 28px;
+            color: #000000;
+            margin-top: 1.75rem;
+          }
+        }
+        .full_screen_button{
+          position: absolute;
+          width: 100%;
+          top: 75%;
+          display: flex;
+          justify-content: center;
+          left: 50%;
+          transform: translateX(-50%);
+          .el-button{
+            cursor: pointer;
+            width: 10rem;
+            height: 2.75rem;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            background: rgba(0,0,0,0.5);
+            box-shadow: 0px 8px 8px 0px rgba(0,0,0,0.25);
+            border-radius: 70px;
+            // border: 4px solid rgba(255,255,255,0.1);
+            font-weight: 500;
+            font-size: 1rem;
+            color: rgba(255,255,255,0.8);
+            padding:0;
+          }
+          .el-button.is-disabled{
+            cursor: not-allowed;
+          }
+          .el-button:hover{
+            border-color: transparent;
+          }
+          .el-button:focus{
+            border-color: transparent;
+          }
+        }
+        .cover_footer {
+          position: absolute;
+          width: 100%;
+          top: 87%;
+          color: #333;
+          text-align: center;
+          .cover_footer_desc {
+            font-weight: 400;
+            font-size: 16px;
+            color: #333333;
+            line-height: 1.4;
+          }
+        }
+      }
+      .area_title
+      {
+        font-weight: bold;
+        font-size: 30px;
+        color: #000000;
+        line-height: 1.4;
+        text-align: center;
+        padding-bottom: 20px;
+      }
+
+      .area_module
+      {
+        width: 100%;
+        height: auto;
+        &.area_module_img{
+          height: 100% !important;
+        }
+        .area_module_title
+        {
+          font-weight: bold;
+          font-size: 19px;
+          color: #000000;
+          padding-bottom: 20px;
+          line-height: 29px;
+          text-align: left;
+        }
+        .area_module_describe
+        {
+          font-weight: 400;
+          font-size: 16px;
+          color: #666666;
+          line-height: 23px;
+          text-align: left;
+          padding-bottom: 0px;
+          border-bottom: 0px solid #F3F3F3;
+          .max_color {
+              color: #3BA272;
+          }
+          .min_color {
+              color: #EE6666;
+          }
+        }
+        .pring_jg{
+          height: 44px;
+          position: relative;
+          &::after{
+            content: '';
+            position: absolute;
+            left: 0;
+            width: 100%;
+            height: 1px;
+            background-color: #F3F3F3;
+            top: 50%;
+            transform: translateY(-50%);
+          }
+        }
+        .area_module_table
+        {
+          &.onLine{
+            .el-table--group, .el-table--border{
+              border: 1px solid #CED8EE;
+            }
+          }
+          width: 100% !important; // 确保表格宽度为 100%
+          // 添加 overflow 属性以处理内容溢出
+          overflow: auto;
+          .el-table--group, .el-table--border{
+            border: 1px solid #CED8EE;
+            border-right: none;
+            border-bottom: none;
+            font-size: 16px;
+            color: #000000;
+          }
+          .el-table::before, .el-table--group::after, .el-table--border::after{
+            background-color:#CED8EE;
+          }
+          .el-table
+          {
+            width: 100%; 
+            border-collapse: collapse;
+            border-radius: 8px;
+            .cell{
+              padding-left: 0;
+              padding-right: 0;
+              line-height: 39px;
+              .right_or_wrong_icon{
+                width: 16px;
+                vertical-align: middle;
+              }
+              &.el-tooltip{
+                min-width:50px;
+              }
+            }
+            // 设置表头样式
+            th {
+                background-color: #F5F7FA; /* 背景颜色 */
+                color: #000000; /* 文字颜色 */
+                padding:0px; /* 内边距 */
+                font-weight: 500;
+                font-size: 16px;
+                line-height: 100%;
+                letter-spacing: 0;
+                text-align: center;
+                border-bottom: 1px solid #CED8EE; /* 底部边框 */
+                border-right: 1px solid #CED8EE;
+                height: 39px;
+                position: relative;
+                .header_row{
+                  display: block;
+                  line-height: 1.4;
+                }
+            }
+            /* 设置表格内容样式 */
+            td {
+                padding: 0px; /* 内边距 */
+                text-align: center; /* 文字对齐方式 */
+                border-bottom:  1px solid #CED8EE; /* 底部边框 */
+                border-right:  1px solid #CED8EE; /* 底部边框 */
+                height: 39px;
+            }
+            /* 设置表格行的悬停效果 */
+            tr:hover {
+              background-color: #f5f5f5; /* 悬停时的背景颜色 */
+            }
+        
+            /* 设置表格的斑马线效果 */
+            tr:nth-child(even) {
+                background-color: #FAFAFA; /* 偶数行的背景颜色 */
+            }
+          }
+        }
+
+        .area_module_chart
+        {
+          width: 100%;
+          height: auto;
+          &.flex{
+            display: flex;
+            justify-content: space-between;
+          }
+          .echart_content
+          {
+            width: 100% !important;
+            min-height: 300px !important;
+          }
+          .module_chart_left{
+            width: 52%;
+          }
+          .module_chart_right{
+            width: 45%;
+          }
+        }
+      }
+      .area_header
+      {
+        width: 100%;
+        margin: auto;
+        height: 62px;
+        background: rgba(238,102,102,0.1);
+        border-radius: 8px;
+        border:1px solid rgba(245,108,108,0.5);
+        font-weight: 600;
+        font-size: 23px;
+        color: #EE6666;
+        text-align: center;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        box-sizing: border-box;
+        position: relative;
+        z-index: 2;
+        &.bg_blue{
+          background: rgba(84,112,198,0.1);
+          border: 1px solid rgba(84,112,198,0.5);
+          color: #5470C6;
+        }
+        &.bg_green{
+          background: rgba(59,162,114,0.1);
+          border: 1px solid rgba(59,162,114,0.5);
+          color: #3BA272;
+        }
+        &.bg_purple{
+          background: rgba(153,95,179,0.1);
+          border: 1px solid rgba(153,95,179,0.5);
+          color: #995FB3;
+        }
+        .header_icon_left
+        {
+          width: 28px;
+          height: 28px;
+          margin-right: 28px;
+        }
+
+        .header_icon_right
+        {
+          width: 28px;
+          height: 28px;
+          margin-left: 28px;
+        }
+        
+      }
+      //考试总览数据
+      .area_info
+      {
+        width: 100%;
+        height: 97px;
+        display: flex;
+        justify-content: space-between;
+        .info_item
+        {
+          width: calc(33.3333% - 8px);
+          border-radius: 4px;
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          padding: 5px;
+          box-sizing: border-box;
+          .item_number
+          {
+            // width: 25%;
+            height: auto;
+            text-align: center;
+            overflow: hidden;
+            white-space: nowrap;
+
+            .number_title
+            {
+              font-weight: 400;
+              font-size: 15px;
+              transform: none;
+              // color: #15325E;
+            }
+            .number_value
+            { 
+              font-weight: 800;
+              font-size: 16px;
+              // color: #15325E;
+              margin-top: 1rem;
+              transform: none;
+            }
+          }
+        }
+        .bg_0
+        {
+          background: linear-gradient( 135deg, #B4D2FF 0%, #F1F7FF 100%);
+          color:#15325E;
+        }
+        .bg_1
+        {
+          background: linear-gradient( 135deg, #FFCAE1 0%, #FFF3F8 100%);
+          color:#5D1638;
+        }
+        .bg_2
+        {
+          background: linear-gradient( 135deg, #D2C6FF 0%, #F7F1FF 100%);
+          color:#41155E;
+        }
+      }
+      //命题分析数据样式
+      .analysis_info
+      {
+        width: 100%;
+        display: flex;
+        justify-content: space-between;
+        .info_item
+        {
+          width: calc((100% - 40px)/5);
+          border-radius: 4px;
+          display: flex;
+          align-items: center;
+          .item_number
+          {
+            width: calc(100% - 20px);
+            margin: auto;
+            height: auto;
+            text-align: left;
+            padding: 20px;
+            .number_title
+            {
+              font-weight: 400;
+              font-size: 12px;
+              
+              line-height: 20px;
+            }
+            .number_value
+            { 
+              font-weight: bold;
+              font-size: 18px;
+            
+              line-height:30px;
+            }
+          }
+        }
+
+        .bg_0
+        {
+          background: linear-gradient( 135deg, #B4D2FF 0%, #F1F7FF 100%);
+          color:#15325E;
+        }
+
+        .bg_1
+        {
+          background: linear-gradient( 135deg, #FFCAE1 0%, #FFF3F8 100%);
+          color:#5D1638;
+        }
+
+        .bg_2
+        {
+          background: linear-gradient( 135deg, #D2C6FF 0%, #F7F1FF 100%);
+          color:#41155E;
+        }
+
+        .bg_3
+        {
+          background: linear-gradient( 135deg, #FFCAE1 0%, #FFF3F8 100%);
+          color:#5D1638;
+        }
+
+        .bg_4
+        {
+          background: linear-gradient( 135deg, #D2C6FF 0%, #F7F1FF 100%);
+          color:#41155E;
+        }
+      }
 
+      .area_line
+      {
+        width: 100%;
+        height: 1px;
+        border-bottom: 1px solid #E9E9E9;
+      }
+
+      .area_page_number
+      {
+        position: absolute;
+        background-color: #FFFFFF;
+        width: 100%;
+        line-height: 30px;
+        height: 41px;
+        padding-bottom: 10px;
+        box-sizing: border-box;
+        bottom: 0;
+        left: 0;
+        text-align: center;
+        color: #000000;
+        font-size: 16px;
+        font-weight: 400;
+      }
+
+      .area_page_title
+      {
+        position: absolute;
+        width: 100%;
+        top: 16px;
+        right: 40px;
+        text-align: right;
+        color: #333333;
+        font-size: 12px;
+        font-weight: 400;
+      }
+
+    }
+  }
+}
+// 联考报告打印 全屏样式
+.report_page{
+  //进入全屏
+  &.full_screen{
+    position: fixed;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    padding:0 !important;
+    z-index: 999999;
+    .joint_print_area{
+      width: 100%;
+    }
+  }
+}
 //成绩小条打印区域
 .print_slip
 {
@@ -2197,6 +3142,9 @@
 {
   background: url('~@/assets/bg/student_report.webp') no-repeat center/cover;
 }
+.student_cover_bg{
+  background: url('~@/assets/bg/student_report_cover.webp') no-repeat center/cover;
+}
 //报告的封面内部样式
 .report_cover {
   width: 100%;
@@ -2213,7 +3161,7 @@
       font-weight: 600;
       font-size: 40px;
       line-height: 64px;
-      white-space: nowrap;
+      // white-space: nowrap;
     }
     .cover_header_title2 {
       font-weight: 500;
@@ -2235,7 +3183,6 @@
     }
   }
 }
-
 /* 分析报告 的目录内部样式 */
 .report_directory {
   box-sizing: border-box;

+ 3352 - 0
src/utils/turn.js

@@ -0,0 +1,3352 @@
+/**
+ * turn.js 4th release
+ * turnjs.com
+ * turnjs.com/license.txt
+ *
+ * Copyright (C) 2012 Emmanuel Garcia
+ * All rights reserved
+ **/
+
+(function($) {
+
+'use strict';
+
+var has3d,
+  
+  hasRot,
+
+  vendor = '',
+  
+  version = '4.1.0',
+
+  PI = Math.PI,
+
+  A90 = PI/2,
+
+  isTouch = 'ontouchstart' in window,
+
+  mouseEvents = (isTouch) ?
+    {
+      down: 'touchstart',
+      move: 'touchmove',
+      up: 'touchend',
+      over: 'touchstart',
+      out: 'touchend'
+    }
+    :
+    {
+      down: 'mousedown',
+      move: 'mousemove',
+      up: 'mouseup',
+      over: 'mouseover',
+      out: 'mouseout'
+    },
+
+  // Contansts used for each corner
+  //   | tl * tr |
+  // l | *     * | r
+  //   | bl * br |
+
+  corners = {
+    backward: ['bl', 'tl'],
+    forward: ['br', 'tr'],
+    all: ['tl', 'bl', 'tr', 'br', 'l', 'r']
+  },
+
+  // Display values  单双页显示
+
+  displays = ['single', 'double'],
+
+  // Direction values  表示方向的值
+
+  directions = ['ltr', 'rtl'],
+
+  // Default options 默认属性
+
+  turnOptions = {
+
+    // Enables hardware acceleration  是否允许硬件加速
+
+    acceleration: true,
+
+    // Display  双页显示
+
+    display: 'double',
+
+    // Duration of transition in milliseconds  翻页速度
+
+    duration: 1000,
+
+    // First page  首页页码
+
+    page: 1,
+    
+    // Enables gradients  是否允许渐变
+
+    gradients: true,
+
+    // Corners used when turning the page  卷边效果
+
+    turnCorners: 'bl,br',
+
+    // Events
+
+    when: null
+  },
+
+  flipOptions = {
+
+    // Size of the active zone of each corner  卷边大小
+    
+    cornerSize: 300
+    
+  },
+
+  // Number of pages in the DOM, minimum value: 6
+
+  pagesInDOM = 6,
+  
+
+turnMethods = {
+
+  // Singleton constructor
+  // $('#selector').turn([options]);
+
+  init: function(options) {
+
+    // Define constants
+    
+    has3d = 'WebKitCSSMatrix' in window || 'MozPerspective' in document.body.style;
+    hasRot = rotationAvailable();
+    vendor = getPrefix();
+
+    var i, that = this, pageNum = 0, data = this.data(), ch = this.children();
+
+    // Set initial configuration
+
+    options = $.extend({
+      width: this.width(),
+      height: this.height(),
+      direction: this.attr('dir') || this.css('direction') || 'ltr'
+    }, turnOptions, options);
+
+    data.opts = options;
+    data.pageObjs = {};
+    data.pages = {};
+    data.pageWrap = {};
+    data.pageZoom = {};
+    data.pagePlace = {};
+    data.pageMv = [];
+    data.zoom = 1;
+    data.totalPages = options.pages || 0;
+    data.eventHandlers = {
+      touchStart: $.proxy(turnMethods._touchStart, this),
+      touchMove: $.proxy(turnMethods._touchMove, this),
+      touchEnd: $.proxy(turnMethods._touchEnd, this),
+      start: $.proxy(turnMethods._eventStart, this)
+    };
+
+
+
+    // Add event listeners
+
+    if (options.when)
+      for (i in options.when)
+        if (has(i, options.when))
+          this.bind(i, options.when[i]);
+
+    // Set the css
+
+    this.css({position: 'relative', width: options.width, height: options.height});
+
+    // Set the initial display
+
+    this.turn('display', options.display);
+
+    // Set the direction
+
+    if (options.direction!=='')
+      this.turn('direction', options.direction);
+    
+    // Prevent blue screen problems of switching to hardware acceleration mode
+    // By forcing hardware acceleration for ever
+
+    if (has3d && !isTouch && options.acceleration)
+      this.transform(translate(0, 0, true));
+
+    // Add pages from the DOM
+
+    for (i = 0; i<ch.length; i++) {
+      if ($(ch[i]).attr('ignore')!='1') {
+        this.turn('addPage', ch[i], ++pageNum);
+      }
+    }
+
+    // Event listeners
+
+    $(this).bind(mouseEvents.down, data.eventHandlers.touchStart).
+      bind('end', turnMethods._eventEnd).
+      bind('pressed', turnMethods._eventPressed).
+      bind('released', turnMethods._eventReleased).
+      bind('flip', turnMethods._flip);
+
+    $(this).parent().bind('start', data.eventHandlers.start);
+
+    $(document).bind(mouseEvents.move, data.eventHandlers.touchMove).
+      bind(mouseEvents.up, data.eventHandlers.touchEnd);
+
+    // Set the initial page
+
+    this.turn('page', options.page);
+
+    // This flipbook is ready
+
+    data.done = true;
+
+    return this;
+  },
+
+  // Adds a page from external data
+
+  addPage: function(element, page) {
+
+    var currentPage,
+      className,
+      incPages = false,
+      data = this.data(),
+      lastPage = data.totalPages+1;
+
+    if (data.destroying)
+      return false;
+
+    // Read the page number from the className of `element` - format: p[0-9]+
+
+    if ((currentPage = /\bp([0-9]+)\b/.exec($(element).attr('class'))))
+      page = parseInt(currentPage[1], 10);
+
+    if (page) {
+      
+      if (page==lastPage)
+        incPages = true;
+      else if (page>lastPage)
+        throw turnError('Page "'+page+'" cannot be inserted');
+
+    } else {
+      
+      page = lastPage;
+      incPages = true;
+
+    }
+
+    if (page>=1 && page<=lastPage) {
+
+      if (data.display=='double')
+        className = (page%2) ? ' odd' : ' even';
+      else
+        className = '';
+
+      // Stop animations
+      if (data.done)
+        this.turn('stop');
+
+      // Move pages if it's necessary
+      if (page in data.pageObjs)
+        turnMethods._movePages.call(this, page, 1);
+
+      // Increase the number of pages
+      if (incPages)
+        data.totalPages = lastPage;
+
+      // Add element
+      data.pageObjs[page] = $(element).
+        css({'float': 'left'}).
+        addClass('page p' + page + className);
+
+      if (!hasHardPage() &&  data.pageObjs[page].hasClass('hard')) {
+        data.pageObjs[page].removeClass('hard');
+      }
+
+      // Add page
+      turnMethods._addPage.call(this, page);
+
+      // Remove pages out of range
+      turnMethods._removeFromDOM.call(this);
+    }
+
+    return this;
+  },
+
+  // Adds a page
+
+  _addPage: function(page) {
+    
+    var data = this.data(),
+      element = data.pageObjs[page];
+
+    if (element)
+      if (turnMethods._necessPage.call(this, page)) {
+
+        if (!data.pageWrap[page]) {
+
+          // Wrapper
+          data.pageWrap[page] = $('<div/>',
+            {'class': 'page-wrapper',
+              page: page,
+              css: {position: 'absolute',
+              overflow: 'hidden'}});
+
+          // Append to this flipbook
+          this.append(data.pageWrap[page]);
+
+          if (!data.pagePlace[page]) {
+            
+            data.pagePlace[page] = page;
+            // Move `pageObjs[page]` to wrapper
+            data.pageObjs[page].appendTo(data.pageWrap[page]);
+          
+         }
+
+          // Set the size of the page
+          var prop = turnMethods._pageSize.call(this, page, true);
+          element.css({width: prop.width, height: prop.height});
+          data.pageWrap[page].css(prop);
+
+        }
+
+        if (data.pagePlace[page] == page) {
+
+         // If the page isn't in another place, create the flip effect
+          turnMethods._makeFlip.call(this, page);
+
+      }
+        
+      } else {
+
+        // Place
+        data.pagePlace[page] = 0;
+
+        // Remove element from the DOM
+        if (data.pageObjs[page])
+          data.pageObjs[page].remove();
+
+      }
+
+  },
+
+  // Checks if a page is in memory
+  
+  hasPage: function(page) {
+
+    return has(page, this.data().pageObjs);
+  
+  },
+
+  // Centers the flipbook
+
+  center: function(page) {
+    
+    var data = this.data(),
+      size = $(this).turn('size'),
+      left = 0;
+
+    if (!data.noCenter) {
+      if (data.display=='double') {
+        var view = this.turn('view', page || data.tpage || data.page);
+
+        if (data.direction=='ltr') {
+          if (!view[0])
+            left -= size.width/4;
+          else if (!view[1])
+            left += size.width/4;
+        } else {
+          if (!view[0])
+            left += size.width/4;
+          else if (!view[1])
+            left -= size.width/4;
+        }
+      
+      }
+
+      $(this).css({marginLeft: left});
+    }
+
+    return this;
+
+  },
+
+  // Destroys the flipbook
+
+  destroy: function () {
+
+    var page,
+      flipbook = this,
+      data = this.data(),
+      events = [
+        'end', 'first', 'flip', 'last', 'pressed',
+        'released', 'start', 'turning', 'turned',
+        'zooming', 'missing'];
+
+    if (trigger('destroying', this)=='prevented')
+      return;
+
+    data.destroying = true;
+
+    $.each(events, function(index, eventName) {
+      flipbook.unbind(eventName);
+    });
+
+    this.parent().unbind('start', data.eventHandlers.start);
+
+    $(document).unbind(mouseEvents.move, data.eventHandlers.touchMove).
+      unbind(mouseEvents.up, data.eventHandlers.touchEnd);
+    
+    while (data.totalPages!==0) {
+      this.turn('removePage', data.totalPages);
+    }
+
+    if (data.fparent)
+      data.fparent.remove();
+
+    if (data.shadow)
+      data.shadow.remove();
+
+    this.removeData();
+    data = null;
+
+    return this;
+
+  },
+
+  // Checks if this element is a flipbook
+
+  is: function() {
+
+    return typeof(this.data().pages)=='object';
+
+  },
+
+  // Sets and gets the zoom value
+
+  zoom: function(newZoom) {
+    
+    var data = this.data();
+
+    if (typeof(newZoom)=='number') {
+
+      if (newZoom<0.001 || newZoom>100)
+        throw turnError(newZoom+ ' is not a value for zoom');
+      
+      if (trigger('zooming', this, [newZoom, data.zoom])=='prevented')
+        return this;
+      
+      var size = this.turn('size'),
+        currentView = this.turn('view'),
+        iz = 1/data.zoom,
+        newWidth = Math.round(size.width * iz * newZoom),
+        newHeight = Math.round(size.height * iz * newZoom);
+    
+      data.zoom = newZoom;
+
+      $(this).turn('stop').
+        turn('size', newWidth, newHeight);
+        /*.
+        css({marginTop: size.height * iz / 2 - newHeight / 2});*/
+
+      if (data.opts.autoCenter)
+        this.turn('center');
+      /*else
+        $(this).css({marginLeft: size.width * iz / 2 - newWidth / 2});*/
+
+      turnMethods._updateShadow.call(this);
+
+      for (var i = 0; i<currentView.length; i++) {
+        if (currentView[i] && data.pageZoom[currentView[i]]!=data.zoom) {
+  
+          this.trigger('zoomed',[
+            currentView[i],
+            currentView,
+            data.pageZoom[currentView[i]],
+            data.zoom]);
+
+          data.pageZoom[currentView[i]] = data.zoom;
+      }
+    }
+
+      return this;
+
+    } else
+      return data.zoom;
+
+  },
+
+  // Gets the size of a page
+
+  _pageSize: function(page, position) {
+
+    var data = this.data(),
+      prop = {};
+
+    if (data.display=='single') {
+
+      prop.width = this.width();
+      prop.height = this.height();
+
+      if (position) {
+        prop.top = 0;
+        prop.left = 0;
+        prop.right = 'auto';
+      }
+
+    } else {
+
+      var pageWidth = this.width()/2,
+        pageHeight = this.height();
+
+      if (data.pageObjs[page].hasClass('own-size')) {
+        prop.width = data.pageObjs[page].width();
+        prop.height = data.pageObjs[page].height();
+      } else {
+        prop.width = pageWidth;
+        prop.height = pageHeight;
+      }
+      
+      if (position) {
+        var odd = page%2;
+        prop.top = (pageHeight-prop.height)/2;
+
+        if (data.direction=='ltr') {
+          
+          prop[(odd) ? 'right' : 'left'] = pageWidth-prop.width;
+          prop[(odd) ? 'left' : 'right'] = 'auto';
+
+        } else {
+          
+          prop[(odd) ? 'left' : 'right'] = pageWidth-prop.width;
+          prop[(odd) ? 'right' : 'left'] = 'auto';
+
+        }
+      
+      }
+    }
+
+    return prop;
+
+  },
+
+  // Prepares the flip effect for a page
+
+  _makeFlip: function(page) {
+
+    var data = this.data();
+
+    if (!data.pages[page] && data.pagePlace[page]==page) {
+      
+      var single = data.display=='single',
+        odd = page%2;
+
+      data.pages[page] = data.pageObjs[page].
+        css(turnMethods._pageSize.call(this, page)).
+        flip({
+          page: page,
+          next: (odd || single) ? page+1 : page-1,
+          turn: this
+        }).
+        flip('disable', data.disabled);
+
+        // Issue about z-index
+        turnMethods._setPageLoc.call(this, page);
+
+        data.pageZoom[page] = data.zoom;
+        
+    }
+
+    return data.pages[page];
+  },
+
+  // Makes pages within a range
+
+  _makeRange: function() {
+
+    var page, range,
+      data = this.data();
+
+    if (data.totalPages<1)
+      return;
+
+    range = this.turn('range');
+
+    for (page = range[0]; page<=range[1]; page++)
+      turnMethods._addPage.call(this, page);
+
+  },
+
+  // Returns a range of pages that should be in the DOM
+  // Example:
+  // - page in the current view, return true
+  // * page is in the range, return true
+  // Otherwise, return false
+  //
+  // 1 2-3 4-5 6-7 8-9 10-11 12-13
+  //   **  **  --   **  **
+
+  range: function(page) {
+
+    var remainingPages, left, right, view,
+      data = this.data();
+
+      page = page || data.tpage || data.page || 1;
+      view = turnMethods._view.call(this, page);
+
+      if (page<1 || page>data.totalPages)
+        throw turnError('"'+page+'" is not a valid page');
+
+    
+      view[1] = view[1] || view[0];
+      
+      if (view[0]>=1 && view[1]<=data.totalPages) {
+
+        remainingPages = Math.floor((pagesInDOM-2)/2);
+
+        if (data.totalPages-view[1] > view[0]) {
+          left = Math.min(view[0]-1, remainingPages);
+          right = 2*remainingPages-left;
+        } else {
+          right = Math.min(data.totalPages-view[1], remainingPages);
+          left = 2*remainingPages-right;
+        }
+
+      } else {
+        left = pagesInDOM-1;
+        right = pagesInDOM-1;
+      }
+
+      return [Math.max(1, view[0]-left),
+          Math.min(data.totalPages, view[1]+right)];
+
+  },
+
+  // Detects if a page is within the range of `pagesInDOM` from the current view
+
+  _necessPage: function(page) {
+    
+    if (page===0)
+      return true;
+
+    var range = this.turn('range');
+
+    return this.data().pageObjs[page].hasClass('fixed') ||
+      (page>=range[0] && page<=range[1]);
+    
+  },
+
+  // Releases memory by removing pages from the DOM
+
+  _removeFromDOM: function() {
+
+    var page, data = this.data();
+
+    for (page in data.pageWrap)
+      if (has(page, data.pageWrap) &&
+        !turnMethods._necessPage.call(this, page))
+      turnMethods._removePageFromDOM.call(this, page);
+    
+  },
+
+  // Removes a page from DOM and its internal references
+
+  _removePageFromDOM: function(page) {
+
+    var data = this.data();
+
+    if (data.pages[page]) {
+      var dd = data.pages[page].data();
+
+      flipMethods._moveFoldingPage.call(data.pages[page], false);
+
+      if (dd.f && dd.f.fwrapper)
+        dd.f.fwrapper.remove();
+
+      data.pages[page].removeData();
+      data.pages[page].remove();
+      delete data.pages[page];
+    }
+
+    if (data.pageObjs[page])
+      data.pageObjs[page].remove();
+
+    if (data.pageWrap[page]) {
+      data.pageWrap[page].remove();
+      delete data.pageWrap[page];
+    }
+
+    turnMethods._removeMv.call(this, page);
+
+    delete data.pagePlace[page];
+    delete data.pageZoom[page];
+
+  },
+
+  // Removes a page
+
+  removePage: function(page) {
+
+    var data = this.data();
+
+    // Delete all the pages
+    if (page=='*') {
+      
+      while (data.totalPages!==0) {
+        this.turn('removePage', data.totalPages);
+      }
+
+    } else {
+
+      if (page<1 || page>data.totalPages)
+        throw turnError('The page '+ page + ' doesn\'t exist');
+        
+      if (data.pageObjs[page]) {
+
+        // Stop animations
+        this.turn('stop');
+
+        // Remove `page`
+        turnMethods._removePageFromDOM.call(this, page);
+
+        delete data.pageObjs[page];
+
+      }
+
+      // Move the pages
+      turnMethods._movePages.call(this, page, -1);
+
+      // Resize the size of this flipbook
+      data.totalPages = data.totalPages-1;
+
+      // Check the current view
+
+      if (data.page>data.totalPages) {
+
+       data.page = null;
+       turnMethods._fitPage.call(this, data.totalPages);
+
+      } else {
+
+        turnMethods._makeRange.call(this);
+        this.turn('update');
+
+      }
+    }
+
+    return this;
+  
+  },
+
+  // Moves pages
+
+  _movePages: function(from, change) {
+
+    var page,
+      that = this,
+      data = this.data(),
+      single = data.display=='single',
+      move = function(page) {
+
+        var next = page + change,
+          odd = next%2,
+          className = (odd) ? ' odd ' : ' even ';
+
+        if (data.pageObjs[page])
+          data.pageObjs[next] = data.pageObjs[page].
+            removeClass('p' + page + ' odd even').
+            addClass('p' + next + className);
+
+        if (data.pagePlace[page] && data.pageWrap[page]) {
+
+          data.pagePlace[next] = next;
+        
+          if (data.pageObjs[next].hasClass('fixed'))
+            data.pageWrap[next] = data.pageWrap[page].
+              attr('page', next);
+          else
+            data.pageWrap[next] = data.pageWrap[page].
+              css(turnMethods._pageSize.call(that, next, true)).
+              attr('page', next);
+      
+            if (data.pages[page])
+              data.pages[next] = data.pages[page].
+                flip('options', {
+                  page: next,
+                  next: (single || odd) ? next+1 : next-1
+                });
+
+            if (change) {
+              delete data.pages[page];
+              delete data.pagePlace[page];
+              delete data.pageZoom[page];
+              delete data.pageObjs[page];
+              delete data.pageWrap[page];
+            }
+
+        }
+
+    };
+
+    if (change>0)
+      for (page=data.totalPages; page>=from; page--)
+        move(page);
+    else
+      for (page=from; page<=data.totalPages; page++)
+        move(page);
+
+  },
+
+  // Sets or Gets the display mode
+
+  display: function(display) {
+
+    var data = this.data(),
+      currentDisplay = data.display;
+
+    if (display===undefined) {
+      
+      return currentDisplay;
+
+    } else {
+
+      if ($.inArray(display, displays)==-1)
+        throw turnError('"'+display + '" is not a value for display');
+      
+      switch(display) {
+        case 'single':
+
+          // Create a temporal page to use as folded page
+
+          if (!data.pageObjs[0]) {
+            this.turn('stop').
+              css({'overflow': 'hidden'});
+
+            data.pageObjs[0] = $('<div />',
+                {'class': 'page p-temporal'}).
+              css({width: this.width(), height: this.height()}).
+              appendTo(this);
+          }
+
+          this.addClass('shadow');
+
+        break;
+        case 'double':
+
+          // Remove the temporal page
+
+          if (data.pageObjs[0]) {
+            this.turn('stop').css({'overflow': ''});
+            data.pageObjs[0].remove();
+            delete data.pageObjs[0];
+          }
+
+          this.removeClass('shadow');
+
+        break;
+      }
+      
+
+      data.display = display;
+
+      if (currentDisplay) {
+        var size = this.turn('size');
+        turnMethods._movePages.call(this, 1, 0);
+        this.turn('size', size.width, size.height).
+          turn('update');
+      }
+
+      return this;
+
+    }
+  
+  },
+  
+  // Gets and sets the direction of the flipbook
+
+  direction: function(dir) {
+
+    var data = this.data();
+
+    if (dir===undefined) {
+
+      return data.direction;
+
+    } else {
+
+      dir = dir.toLowerCase();
+
+      if ($.inArray(dir, directions)==-1)
+        throw turnError('"' + dir + '" is not a value for direction');
+
+      if (dir=='rtl') {
+        $(this).attr('dir', 'ltr').
+          css({direction: 'ltr'});
+      }
+
+      data.direction = dir;
+
+      if (data.done)
+        this.turn('size', $(this).width(), $(this).height());
+
+      return this;
+    }
+
+  },
+
+  // Detects animation
+
+  animating: function() {
+
+    return this.data().pageMv.length>0;
+
+  },
+
+  // Gets the current activated corner
+
+  corner: function() {
+    
+    var corner,
+      page,
+      data = this.data();
+
+    for (page in data.pages) {
+      if (has(page, data.pages))
+        if ((corner = data.pages[page].flip('corner'))) {
+          return corner;
+        }
+    }
+
+    return false;
+  },
+
+  // Gets the data stored in the flipbook
+
+  data: function() {
+    
+    return this.data();
+
+  },
+
+  // Disables and enables the effect
+
+  disable: function(disable) {
+
+    var page,
+      data = this.data(),
+      view = this.turn('view');
+
+    data.disabled = disable===undefined || disable===true;
+
+    for (page in data.pages) {
+      if (has(page, data.pages))
+        data.pages[page].flip('disable',
+          (data.disabled) ? true : $.inArray(parseInt(page, 10), view)==-1);
+    }
+
+    return this;
+
+  },
+
+  // Disables and enables the effect
+
+  disabled: function(disable) {
+
+    if (disable===undefined) {
+      return this.data().disabled===true;
+    } else {
+      return this.turn('disable', disable);
+    }
+
+  },
+
+  // Gets and sets the size
+
+  size: function(width, height) {
+
+    if (width===undefined || height===undefined) {
+      
+      return {width: this.width(), height: this.height()};
+
+    } else {
+
+      this.turn('stop');
+
+      var page, prop,
+        data = this.data(),
+        pageWidth = (data.display=='double') ? width/2 : width;
+
+      this.css({width: width, height: height});
+
+      if (data.pageObjs[0])
+        data.pageObjs[0].css({width: pageWidth, height: height});
+      
+      for (page in data.pageWrap) {
+        if (!has(page, data.pageWrap)) continue;
+
+        prop = turnMethods._pageSize.call(this, page, true);
+
+        data.pageObjs[page].css({width: prop.width, height: prop.height});
+        data.pageWrap[page].css(prop);
+        
+        if (data.pages[page])
+          data.pages[page].css({width: prop.width, height: prop.height});
+      }
+
+      this.turn('resize');
+
+      return this;
+
+    }
+  },
+
+  // Resizes each page
+
+  resize: function() {
+
+    var page, data = this.data();
+
+    if (data.pages[0]) {
+      data.pageWrap[0].css({left: -this.width()});
+      data.pages[0].flip('resize', true);
+    }
+
+    for (page = 1; page <= data.totalPages; page++)
+      if (data.pages[page])
+        data.pages[page].flip('resize', true);
+
+    turnMethods._updateShadow.call(this);
+
+    if (data.opts.autoCenter)
+      this.turn('center');
+
+  },
+
+  // Removes an animation from the cache
+
+  _removeMv: function(page) {
+
+    var i, data = this.data();
+      
+    for (i=0; i<data.pageMv.length; i++)
+      if (data.pageMv[i]==page) {
+        data.pageMv.splice(i, 1);
+        return true;
+      }
+
+    return false;
+
+  },
+
+  // Adds an animation to the cache
+  
+  _addMv: function(page) {
+
+    var data = this.data();
+
+    turnMethods._removeMv.call(this, page);
+    data.pageMv.push(page);
+
+  },
+
+  // Gets indexes for a view
+
+  _view: function(page) {
+  
+    var data = this.data();
+    
+    page = page || data.page;
+
+    if (data.display=='double')
+      return (page%2) ? [page-1, page] : [page, page+1];
+    else
+      return [page];
+
+  },
+
+  // Gets a view
+
+  view: function(page) {
+
+    var data = this.data(),
+      view = turnMethods._view.call(this, page);
+
+    if (data.display=='double')
+      return [(view[0]>0) ? view[0] : 0,
+        (view[1]<=data.totalPages) ? view[1] : 0];
+    else
+      return [(view[0]>0 && view[0]<=data.totalPages) ? view[0] : 0];
+
+  },
+
+  // Stops animations
+
+  stop: function(ignore, animate) {
+
+    if (this.turn('animating')) {
+  
+      var i, opts, page,
+        data = this.data();
+
+      if (data.tpage) {
+        data.page = data.tpage;
+        delete data['tpage'];
+      }
+
+      for (i = 0; i<data.pageMv.length; i++) {
+
+        if (!data.pageMv[i] || data.pageMv[i]===ignore)
+          continue;
+
+        page = data.pages[data.pageMv[i]];
+        opts = page.data().f.opts;
+
+        page.flip('hideFoldedPage', animate);
+
+        if (!animate)
+          flipMethods._moveFoldingPage.call(page, false);
+
+        if (opts.force) {
+          opts.next = (opts.page%2===0) ? opts.page-1 : opts.page+1;
+          delete opts['force'];
+        }
+
+      }
+    }
+    
+    this.turn('update');
+
+    return this;
+  },
+
+  // Gets and sets the number of pages
+
+  pages: function(pages) {
+
+    var data = this.data();
+
+    if (pages) {
+
+      if (pages<data.totalPages) {
+
+        for (var page = data.totalPages; page>pages; page--)
+          this.turn('removePage', page);
+
+      }
+
+        data.totalPages = pages;
+        turnMethods._fitPage.call(this, data.page);
+
+      return this;
+
+    } else
+      return data.totalPages;
+
+  },
+
+  // Checks missing pages
+
+  _missing : function(page) {
+    
+    var data = this.data();
+
+    if (data.totalPages<1)
+      return;
+
+    var p,
+        range = this.turn('range', page),
+        missing = [];
+
+    for (p = range[0]; p<=range[1]; p++) {
+      if (!data.pageObjs[p])
+        missing.push(p);
+    }
+
+    if (missing.length>0)
+      this.trigger('missing', [missing]);
+
+  },
+
+  // Sets a page without effect
+
+  _fitPage: function(page) {
+
+    var data = this.data(),
+      newView = this.turn('view', page);
+
+    turnMethods._missing.call(this, page);
+    
+    if (!data.pageObjs[page])
+      return;
+
+    data.page = page;
+  
+    this.turn('stop');
+
+    for (var i = 0; i<newView.length; i++) {
+
+      if (newView[i] && data.pageZoom[newView[i]]!=data.zoom) {
+  
+        this.trigger('zoomed',[
+          newView[i],
+          newView,
+          data.pageZoom[newView[i]],
+          data.zoom]);
+
+        data.pageZoom[newView[i]] = data.zoom;
+
+      }
+    }
+
+    turnMethods._removeFromDOM.call(this);
+    turnMethods._makeRange.call(this);
+    turnMethods._updateShadow.call(this);
+    this.trigger('turned', [page, newView]);
+    this.turn('update');
+
+    if (data.opts.autoCenter)
+      this.turn('center');
+
+  },
+  
+  // Turns the page
+
+  _turnPage: function(page) {
+
+    var current,
+      next,
+      data = this.data(),
+      place = data.pagePlace[page],
+      view = this.turn('view'),
+      newView = this.turn('view', page);
+
+
+    if (data.page!=page) {
+
+      var currentPage = data.page;
+
+      if (trigger('turning', this, [page, newView])=='prevented') {
+
+        if (currentPage==data.page && $.inArray(place, data.pageMv)!=-1)
+          data.pages[place].flip('hideFoldedPage', true);
+        
+        return;
+
+      }
+
+      if ($.inArray(1, newView)!=-1)
+        this.trigger('first');
+      if ($.inArray(data.totalPages, newView)!=-1)
+        this.trigger('last');
+
+    }
+
+    if (data.display=='single') {
+      current = view[0];
+      next = newView[0];
+    } else if (view[1] && page>view[1]) {
+      current = view[1];
+      next = newView[0];
+    } else if (view[0] && page<view[0]) {
+      current = view[0];
+      next = newView[1];
+    }
+
+    var optsCorners = data.opts.turnCorners.split(','),
+      flipData = data.pages[current].data().f,
+      opts = flipData.opts,
+      actualPoint = flipData.point;
+
+    turnMethods._missing.call(this, page);
+    
+    if (!data.pageObjs[page])
+      return;
+
+    this.turn('stop');
+
+    data.page = page;
+
+    turnMethods._makeRange.call(this);
+
+    data.tpage = next;
+
+    if (opts.next!=next) {
+      opts.next = next;
+      opts.force = true;
+    }
+
+    this.turn('update');
+
+    flipData.point = actualPoint;
+    
+    if (flipData.effect=='hard')
+      if (data.direction=='ltr')
+        data.pages[current].flip('turnPage',
+          (page>current) ? 'r' : 'l');
+      else
+        data.pages[current].flip('turnPage',
+          (page>current) ? 'l' : 'r');
+    else {
+      if (data.direction=='ltr')
+        data.pages[current].flip('turnPage',
+          optsCorners[(page>current) ? 1 : 0]);
+      else
+        data.pages[current].flip('turnPage',
+          optsCorners[(page>current) ? 0 : 1]);
+    }
+
+  },
+
+  // Gets and sets a page
+
+  page: function(page) {
+
+    var data = this.data();
+
+    if (page===undefined) {
+      
+      return data.page;
+
+    } else {
+
+      if (!data.disabled && !data.destroying) {
+
+        page = parseInt(page, 10);
+
+        if (page>0 && page<=data.totalPages) {
+
+          if (page!=data.page) {
+            if (!data.done || $.inArray(page, this.turn('view'))!=-1)
+              turnMethods._fitPage.call(this, page);
+            else
+              turnMethods._turnPage.call(this, page);
+          }
+      
+          return this;
+
+        } else {
+        
+          throw turnError('The page ' + page + ' does not exist');
+
+        }
+
+      }
+
+    }
+
+  },
+
+  // Turns to the next view
+
+  next: function() {
+
+    return this.turn('page', Math.min(this.data().totalPages,
+      turnMethods._view.call(this, this.data().page).pop() + 1));
+  
+  },
+
+  // Turns to the previous view
+
+  previous: function() {
+
+    return this.turn('page', Math.max(1,
+      turnMethods._view.call(this, this.data().page).shift() - 1));
+
+  },
+
+  // Shows a peeling corner
+
+  peel: function(corner, animate) {
+    
+    var data = this.data(),
+      view = this.turn('view');
+
+    animate = (animate===undefined) ? true : animate===true;
+
+    if (corner===false) {
+      
+      this.turn('stop', null, animate);
+
+    } else {
+    
+      if (data.display=='single') {
+
+        data.pages[data.page].flip('peel', corner, animate);
+
+      } else {
+
+        var page;
+
+        if (data.direction=='ltr') {
+          
+          page = (corner.indexOf('l')!=-1) ? view[0] : view[1];
+
+        } else {
+          
+          page = (corner.indexOf('l')!=-1) ? view[1] : view[0];
+
+        }
+        
+        if (data.pages[page])
+          data.pages[page].flip('peel', corner, animate);
+
+      }
+    }
+
+    return this;
+
+  },
+
+  // Adds a motion to the internal list
+  // This event is called in context of flip
+
+  _addMotionPage: function() {
+
+    var opts = $(this).data().f.opts,
+      turn = opts.turn,
+      dd = turn.data();
+
+    turnMethods._addMv.call(turn, opts.page);
+
+  },
+
+  // This event is called in context of flip
+
+  _eventStart: function(e, opts, corner) {
+
+    var data = opts.turn.data(),
+      actualZoom = data.pageZoom[opts.page];
+
+    if (e.isDefaultPrevented()) {
+      turnMethods._updateShadow.call(opts.turn);
+      return;
+    }
+
+    if (actualZoom && actualZoom!=data.zoom) {
+      
+      opts.turn.trigger('zoomed',[
+        opts.page,
+        opts.turn.turn('view', opts.page),
+        actualZoom,
+        data.zoom]);
+
+      data.pageZoom[opts.page] = data.zoom;
+
+    }
+
+    if (data.display=='single' && corner) {
+
+      if ((corner.charAt(1)=='l' && data.direction=='ltr') ||
+        (corner.charAt(1)=='r' && data.direction=='rtl'))
+      {
+        
+        opts.next = (opts.next<opts.page) ? opts.next : opts.page-1;
+        opts.force = true;
+
+      } else {
+        
+        opts.next = (opts.next>opts.page) ? opts.next : opts.page+1;
+
+      }
+
+    }
+
+    turnMethods._addMotionPage.call(e.target);
+    turnMethods._updateShadow.call(opts.turn);
+  },
+
+  // This event is called in context of flip
+
+  _eventEnd: function(e, opts, turned) {
+  
+    var that = $(e.target),
+      data = that.data().f,
+      turn = opts.turn,
+      dd = turn.data();
+
+    if (turned) {
+
+      var tpage = dd.tpage || dd.page;
+    
+      if (tpage==opts.next || tpage==opts.page) {
+        delete dd.tpage;
+
+        turnMethods._fitPage.call(turn, tpage || opts.next, true);
+      }
+
+    } else {
+      
+      turnMethods._removeMv.call(turn, opts.page);
+      turnMethods._updateShadow.call(turn);
+      turn.turn('update');
+
+    }
+    
+  },
+  
+  // This event is called in context of flip
+
+  _eventPressed: function(e) {
+
+    var page,
+      data = $(e.target).data().f,
+      turn = data.opts.turn,
+      turnData = turn.data(),
+      pages = turnData.pages;
+    
+    turnData.mouseAction = true;
+
+    turn.turn('update');
+
+    return data.time = new Date().getTime();
+
+  },
+
+  // This event is called in context of flip
+
+  _eventReleased: function(e, point) {
+
+    var outArea,
+      page = $(e.target),
+      data = page.data().f,
+      turn = data.opts.turn,
+      turnData = turn.data();
+    
+    if (turnData.display=='single') {
+      outArea = (point.corner=='br' || point.corner=='tr') ?
+        point.x<page.width()/2:
+        point.x>page.width()/2;
+    } else {
+      outArea = point.x<0 || point.x>page.width();
+    }
+
+    if ((new Date()).getTime()-data.time<200 || outArea) {
+
+      e.preventDefault();
+      turnMethods._turnPage.call(turn, data.opts.next);
+
+    }
+
+    turnData.mouseAction = false;
+
+  },
+
+  // This event is called in context of flip
+  
+  _flip: function(e) {
+
+    e.stopPropagation();
+
+    var opts = $(e.target).data().f.opts;
+
+    opts.turn.trigger('turn', [opts.next]);
+
+    if (opts.turn.data().opts.autoCenter) {
+      opts.turn.turn('center', opts.next);
+    }
+
+  },
+
+ //
+  _touchStart: function() {
+    var data = this.data();
+    for (var page in data.pages) {
+      if (has(page, data.pages) &&
+        flipMethods._eventStart.apply(data.pages[page], arguments)===false) {
+          return false;
+      }
+    }
+  },
+  
+  //
+  _touchMove: function() {
+    var data = this.data();
+    for (var page in data.pages) {
+      if (has(page, data.pages)) {
+        flipMethods._eventMove.apply(data.pages[page], arguments);
+      }
+    }
+  },
+
+  //
+  _touchEnd: function() {
+    var data = this.data();
+    for (var page in data.pages) {
+      if (has(page, data.pages)) {
+        flipMethods._eventEnd.apply(data.pages[page], arguments);
+      }
+    }
+  },
+
+  // Calculate the z-index value for pages during the animation
+
+  calculateZ: function(mv) {
+
+    var i, page, nextPage, placePage, dpage,
+      that = this,
+      data = this.data(),
+      view = this.turn('view'),
+      currentPage = view[0] || view[1],
+      total = mv.length-1,
+      r = {pageZ: {}, partZ: {}, pageV: {}},
+
+      addView = function(page) {
+        var view = that.turn('view', page);
+        if (view[0]) r.pageV[view[0]] = true;
+        if (view[1]) r.pageV[view[1]] = true;
+      };
+    
+    for (i = 0; i<=total; i++) {
+      page = mv[i];
+      nextPage = data.pages[page].data().f.opts.next;
+      placePage = data.pagePlace[page];
+      addView(page);
+      addView(nextPage);
+      dpage = (data.pagePlace[nextPage]==nextPage) ? nextPage : page;
+      r.pageZ[dpage] = data.totalPages - Math.abs(currentPage-dpage);
+      r.partZ[placePage] = data.totalPages*2 -  total + i;
+    }
+
+    return r;
+  },
+
+  // Updates the z-index and display property of every page
+
+  update: function() {
+
+    var page,
+      data = this.data();
+
+    if (this.turn('animating') && data.pageMv[0]!==0) {
+
+      // Update motion
+
+      var p, apage, fixed,
+        pos = this.turn('calculateZ', data.pageMv),
+        corner = this.turn('corner'),
+        actualView = this.turn('view'),
+        newView = this.turn('view', data.tpage);
+  
+      for (page in data.pageWrap) {
+
+        if (!has(page, data.pageWrap))
+          continue;
+
+        fixed = data.pageObjs[page].hasClass('fixed');
+
+        data.pageWrap[page].css({
+          display: (pos.pageV[page] || fixed) ? '' : 'none',
+          zIndex:
+            (data.pageObjs[page].hasClass('hard') ?
+              pos.partZ[page]
+              :
+              pos.pageZ[page]
+            ) || (fixed ? -1 : 0)
+        });
+
+        if ((p = data.pages[page])) {
+
+          p.flip('z', pos.partZ[page] || null);
+
+          if (pos.pageV[page])
+            p.flip('resize');
+          
+          if (data.tpage) { // Is it turning the page to `tpage`?
+
+            p.flip('hover', false).
+              flip('disable',
+                $.inArray(parseInt(page, 10), data.pageMv)==-1 &&
+                page!=newView[0] &&
+                page!=newView[1]);
+
+          } else {
+
+            p.flip('hover', corner===false).
+              flip('disable', page!=actualView[0] && page!=actualView[1]);
+
+          }
+
+        }
+
+      }
+
+    } else {
+
+      // Update static pages
+
+      for (page in data.pageWrap) {
+
+        if (!has(page, data.pageWrap))
+          continue;
+
+        var pageLocation = turnMethods._setPageLoc.call(this, page);
+
+        if (data.pages[page]) {
+          data.pages[page].
+            flip('disable', data.disabled || pageLocation!=1).
+            flip('hover', true).
+            flip('z', null);
+        }
+      }
+    }
+
+    return this;
+  },
+
+  // Updates the position and size of the flipbook's shadow
+
+  _updateShadow: function() {
+    
+    var view, view2, shadow,
+      data = this.data(),
+      width = this.width(),
+      height = this.height(),
+      pageWidth = (data.display=='single') ? width : width/2;
+
+    view = this.turn('view');
+
+    if (!data.shadow) {
+      data.shadow = $('<div />', {
+          'class': 'shadow',
+          'css': divAtt(0, 0, 0).css
+        }).
+        appendTo(this);
+    }
+
+    for (var i = 0; i<data.pageMv.length; i++) {
+      if (!view[0] || !view[1])
+        break;
+    
+      view = this.turn('view', data.pages[data.pageMv[i]].data().f.opts.next);
+      view2 = this.turn('view', data.pageMv[i]);
+
+      view[0] = view[0] && view2[0];
+      view[1] = view[1] && view2[1];
+    }
+
+    if (!view[0]) shadow = (data.direction=='ltr') ? 1 : 2;
+    else if (!view[1]) shadow = (data.direction=='ltr') ? 2 : 1;
+    else shadow = 3;
+
+    switch (shadow) {
+      case 1:
+        data.shadow.css({
+          width: pageWidth,
+          height: height,
+          top: 0,
+          left: pageWidth
+        });
+        break;
+      case 2:
+        data.shadow.css({
+          width: pageWidth,
+          height: height,
+          top: 0,
+          left: 0
+        });
+        break;
+      case 3:
+        data.shadow.css({
+          width: width,
+          height: height,
+          top: 0,
+          left: 0
+        });
+        break;
+    }
+
+  },
+
+  // Sets the z-index and display property of a page
+  // It depends on the current view
+
+  _setPageLoc: function(page) {
+
+    var data = this.data(),
+      view = this.turn('view'),
+      loc = 0;
+
+    
+    if (page==view[0] || page==view[1])
+      loc = 1;
+    else if (
+      (data.display=='single' && page==view[0]+1) ||
+      (data.display=='double' && page==view[0]-2 || page==view[1]+2)
+    )
+      loc = 2;
+
+    if (!this.turn('animating'))
+      switch (loc) {
+        case 1:
+          data.pageWrap[page].css(
+          {
+            zIndex: data.totalPages,
+            display: ''
+          });
+        break;
+        case 2:
+          data.pageWrap[page].css(
+          {
+            zIndex: data.totalPages-1,
+            display: ''
+          });
+        break;
+        case 0:
+          data.pageWrap[page].css(
+          {
+            zIndex: 0,
+            display: (data.pageObjs[page].hasClass('fixed')) ? '' : 'none'}
+          );
+        break;
+      }
+    
+    return loc;
+  },
+
+  // Gets and sets the options
+
+  options: function(options) {
+    
+    if (options===undefined) {
+      
+      return this.data().opts;
+
+    } else {
+
+      var data = this.data();
+
+      // Set new values
+
+      $.extend(data.opts, options);
+      
+      // Set pages
+
+      if (options.pages)
+        this.turn('pages', options.pages);
+
+      // Set page
+
+      if (options.page)
+        this.turn('page', options.page);
+
+      // Set display
+
+      if (options.display)
+        this.turn('display', options.display);
+      
+      // Set direction
+
+      if (options.direction)
+        this.turn('direction', options.direction);
+
+      // Set size
+
+      if (options.width && options.height)
+        this.turn('size', options.width, options.height);
+      
+      // Add event listeners
+
+      if (options.when)
+        for (var eventName in options.when)
+          if (has(eventName, options.when)) {
+            this.unbind(eventName).
+              bind(eventName, options.when[eventName]);
+          }
+
+      return this;
+    }
+
+  },
+
+  // Gets the current version
+
+  version: function() {
+
+    return version;
+
+  }
+},
+
+// Methods and properties for the flip page effect
+
+flipMethods = {
+
+  // Constructor
+
+  init: function(opts) {
+
+    this.data({f: {
+      disabled: false,
+      hover: false,
+      effect: (this.hasClass('hard')) ? 'hard' : 'sheet'
+    }});
+  
+    this.flip('options', opts);
+
+    flipMethods._addPageWrapper.call(this);
+
+    return this;
+  },
+
+  setData: function(d) {
+    
+    var data = this.data();
+
+    data.f = $.extend(data.f, d);
+
+    return this;
+  },
+
+  options: function(opts) {
+    
+    var data = this.data().f;
+
+    if (opts) {
+      flipMethods.setData.call(this,
+        {opts: $.extend({}, data.opts || flipOptions, opts)});
+      return this;
+    } else
+      return data.opts;
+
+  },
+
+  z: function(z) {
+    
+    var data = this.data().f;
+
+    data.opts['z-index'] = z;
+
+    if (data.fwrapper)
+      data.fwrapper.css({
+        zIndex: z || parseInt(data.parent.css('z-index'), 10) || 0
+      });
+
+    return this;
+  },
+
+  _cAllowed: function() {
+    
+    var data = this.data().f,
+      page = data.opts.page,
+      turnData = data.opts.turn.data(),
+      odd = page%2;
+    
+    if (data.effect=='hard') {
+    
+      return (turnData.direction=='ltr') ?
+        [(odd) ? 'r' : 'l'] :
+        [(odd) ? 'l' : 'r'];
+
+    } else {
+
+      if (turnData.display=='single') {
+
+        if (page==1)
+          return (turnData.direction=='ltr') ?
+            corners['forward'] : corners['backward'];
+        else if (page==turnData.totalPages)
+          return (turnData.direction=='ltr') ?
+            corners['backward'] : corners['forward'];
+        else
+          return corners['all'];
+
+      } else {
+
+        return (turnData.direction=='ltr') ?
+          corners[(odd) ? 'forward' : 'backward']
+          :
+          corners[(odd) ? 'backward' : 'forward'];
+
+      }
+
+    }
+
+  },
+
+  _cornerActivated: function(p) {
+
+    var data = this.data().f,
+      width = this.width(),
+      height = this.height(),
+      point = {x: p.x, y: p.y, corner: ''},
+      csz = data.opts.cornerSize;
+
+    if (point.x<=0 || point.y<=0 || point.x>=width || point.y>=height)
+      return false;
+
+    var allowedCorners = flipMethods._cAllowed.call(this);
+
+    switch (data.effect) {
+      case 'hard':
+        
+        if (point.x>width-csz)
+          point.corner = 'r';
+        else if (point.x<csz)
+          point.corner = 'l';
+        else
+          return false;
+            
+        break;
+
+      case 'sheet':
+        
+        if (point.y<csz)
+          point.corner+= 't';
+        else if (point.y>=height-csz)
+          point.corner+= 'b';
+        else
+          return false;
+    
+        if (point.x<=csz)
+          point.corner+= 'l';
+        else if (point.x>=width-csz)
+          point.corner+= 'r';
+        else
+          return false;
+     
+        break;
+      }
+
+    return (!point.corner || $.inArray(point.corner, allowedCorners)==-1) ?
+      false : point;
+
+  },
+
+  _isIArea: function(e) {
+
+    var pos = this.data().f.parent.offset();
+
+    e = (isTouch && e.originalEvent) ? e.originalEvent.touches[0] : e;
+
+    return flipMethods._cornerActivated.call(this,
+      {
+        x: e.pageX-pos.left,
+        y: e.pageY-pos.top
+      });
+
+  },
+
+  _c: function(corner, opts) {
+
+    opts = opts || 0;
+
+    switch (corner) {
+      case 'tl':
+        return point2D(opts, opts);
+      case 'tr':
+        return point2D(this.width()-opts, opts);
+      case 'bl':
+        return point2D(opts, this.height()-opts);
+      case 'br':
+        return point2D(this.width()-opts, this.height()-opts);
+      case 'l':
+        return point2D(opts, 0);
+      case 'r':
+        return point2D(this.width()-opts, 0);
+    }
+
+  },
+
+  _c2: function(corner) {
+
+    switch (corner) {
+      case 'tl':
+        return point2D(this.width()*2, 0);
+      case 'tr':
+        return point2D(-this.width(), 0);
+      case 'bl':
+        return point2D(this.width()*2, this.height());
+      case 'br':
+        return point2D(-this.width(), this.height());
+      case 'l':
+        return point2D(this.width()*2, 0);
+      case 'r':
+        return point2D(-this.width(), 0);
+    }
+
+  },
+
+  _foldingPage: function() {
+
+    var data = this.data().f;
+
+    if (!data)
+      return;
+
+    var opts = data.opts;
+
+    if (opts.turn) {
+      data = opts.turn.data();
+      if (data.display == 'single')
+        return (opts.next>1 || opts.page>1) ? data.pageObjs[0] : null;
+      else
+        return data.pageObjs[opts.next];
+    }
+
+  },
+
+  _backGradient: function() {
+
+    var data = this.data().f,
+      turnData = data.opts.turn.data(),
+      gradient =  turnData.opts.gradients && (turnData.display=='single' ||
+         (data.opts.page!=2 && data.opts.page!=turnData.totalPages-1));
+
+    if (gradient && !data.bshadow)
+      data.bshadow = $('<div/>', divAtt(0, 0, 1)).
+        css({'position': '', width: this.width(), height: this.height()}).
+        appendTo(data.parent);
+
+    return gradient;
+
+  },
+
+  type: function () {
+    
+    return this.data().f.effect;
+
+  },
+
+  resize: function(full) {
+    
+    var data = this.data().f,
+      turnData = data.opts.turn.data(),
+      width = this.width(),
+      height = this.height();
+
+    switch (data.effect) {
+      case 'hard':
+        
+      if (full) {
+        data.wrapper.css({width: width, height: height});
+        data.fpage.css({width: width, height: height});
+        if (turnData.opts.gradients) {
+          data.ashadow.css({width: width, height: height});
+          data.bshadow.css({width: width, height: height});
+        }
+      }
+
+      break;
+      case 'sheet':
+
+      if (full) {
+        var size = Math.round(Math.sqrt(Math.pow(width, 2)+Math.pow(height, 2)));
+
+        data.wrapper.css({width: size, height: size});
+        data.fwrapper.css({width: size, height: size}).
+          children(':first-child').
+          css({width: width, height: height});
+
+        data.fpage.css({width: width, height: height});
+
+        if (turnData.opts.gradients)
+          data.ashadow.css({width: width, height: height});
+
+        if (flipMethods._backGradient.call(this))
+          data.bshadow.css({width: width, height: height});
+      }
+
+      if (data.parent.is(':visible')) {
+        var offset = findPos(data.parent[0]);
+
+        data.fwrapper.css({top: offset.top,
+          left: offset.left});
+
+        //if (data.opts.turn) {
+         offset = findPos(data.opts.turn[0]);
+         data.fparent.css({top: -offset.top, left: -offset.left});
+        //}
+      }
+
+      this.flip('z', data.opts['z-index']);
+
+      break;
+    }
+
+  },
+
+  // Prepares the page by adding a general wrapper and another objects
+
+  _addPageWrapper: function() {
+
+    var att,
+      data = this.data().f,
+      turnData = data.opts.turn.data(),
+      parent = this.parent();
+
+    data.parent = parent;
+
+  if (!data.wrapper)
+    switch (data.effect) {
+      case 'hard':
+        
+        var cssProperties = {};
+        cssProperties[vendor + 'transform-style'] = 'preserve-3d';
+        cssProperties[vendor + 'backface-visibility'] = 'hidden';
+
+        data.wrapper = $('<div/>', divAtt(0, 0, 2)).
+          css(cssProperties).
+          appendTo(parent).
+          prepend(this);
+
+        data.fpage = $('<div/>', divAtt(0, 0, 1)).
+          css(cssProperties).
+          appendTo(parent);
+        
+        if (turnData.opts.gradients) {
+          data.ashadow = $('<div/>', divAtt(0, 0,  0)).
+            hide().
+            appendTo(parent);
+
+          data.bshadow = $('<div/>', divAtt(0, 0,  0));
+        }
+
+      break;
+      case 'sheet':
+        
+        var width = this.width(),
+          height = this.height(),
+          size = Math.round(Math.sqrt(Math.pow(width, 2)+Math.pow(height, 2)));
+        
+        data.fparent = data.opts.turn.data().fparent;
+
+        if (!data.fparent) {
+          var fparent = $('<div/>', {css: {'pointer-events': 'none'}}).hide();
+            fparent.data().flips = 0;
+            fparent.css(divAtt(0, 0, 'auto', 'visible').css).
+            appendTo(data.opts.turn);
+            
+            data.opts.turn.data().fparent = fparent;
+            data.fparent = fparent;
+        }
+
+        this.css({position: 'absolute', top: 0, left: 0, bottom: 'auto', right: 'auto'});
+
+        data.wrapper = $('<div/>', divAtt(0, 0, this.css('z-index'))).
+          appendTo(parent).
+          prepend(this);
+
+        data.fwrapper = $('<div/>', divAtt(parent.offset().top, parent.offset().left)).
+          hide().
+          appendTo(data.fparent);
+
+        data.fpage = $('<div/>', divAtt(0, 0, 0, 'visible')).
+          css({cursor: 'default'}).
+          appendTo(data.fwrapper);
+
+        if (turnData.opts.gradients)
+          data.ashadow = $('<div/>', divAtt(0, 0,  1)).
+          appendTo(data.fpage);
+
+        flipMethods.setData.call(this, data);
+
+      break;
+    }
+
+    // Set size
+    flipMethods.resize.call(this, true);
+
+  },
+
+  // Takes a 2P point from the screen and applies the transformation
+
+  _fold: function(point) {
+
+    var data = this.data().f,
+      turnData = data.opts.turn.data(),
+      o = flipMethods._c.call(this, point.corner),
+      width = this.width(),
+      height = this.height();
+
+    switch (data.effect) {
+
+      case 'hard':
+
+        if (point.corner=='l')
+          point.x = Math.min(Math.max(point.x, 0), width*2);
+        else
+          point.x = Math.max(Math.min(point.x, width), -width);
+
+        var leftPos,
+          shadow,
+          gradientX,
+          fpageOrigin,
+          parentOrigin,
+          totalPages = turnData.totalPages,
+          zIndex = data.opts['z-index'] || totalPages,
+          parentCss = {'overflow': 'visible'},
+          relX = (o.x) ? (o.x - point.x)/width : point.x/width,
+          angle = relX * 90,
+          half = angle<90;
+
+        switch (point.corner) {
+          case 'l':
+
+            fpageOrigin =  '0% 50%';
+            parentOrigin =  '100% 50%';
+
+            if (half) {
+              leftPos = 0;
+              shadow = data.opts.next-1>0;
+              gradientX = 1;
+            } else {
+              leftPos = '100%';
+              shadow = data.opts.page+1<totalPages;
+              gradientX = 0;
+            }
+
+          break;
+          case 'r':
+
+            fpageOrigin =  '100% 50%';
+            parentOrigin =  '0% 50%';
+            angle = -angle;
+            width = -width;
+
+            if (half) {
+              leftPos = 0;
+              shadow = data.opts.next+1<totalPages;
+              gradientX = 0;
+            } else {
+              leftPos = '-100%';
+              shadow = data.opts.page!=1;
+              gradientX = 1;
+            }
+
+          break;
+        }
+
+        parentCss[vendor+'perspective-origin'] = parentOrigin;
+
+        data.wrapper.transform('rotateY('+angle+'deg)' +
+          'translate3d(0px, 0px, '+(this.attr('depth')||0)+'px)', parentOrigin);
+
+        data.fpage.transform('translateX('+width+'px) rotateY('+(180+angle)+'deg)', fpageOrigin);
+
+        data.parent.css(parentCss);
+
+        if (half) {
+          relX = -relX+1;
+          data.wrapper.css({zIndex: zIndex+1});
+          data.fpage.css({zIndex: zIndex});
+        } else {
+          relX = relX-1;
+          data.wrapper.css({zIndex: zIndex});
+          data.fpage.css({zIndex: zIndex+1});
+        }
+
+        if (turnData.opts.gradients) {
+          if (shadow)
+            data.ashadow.css({
+              display: '',
+              left: leftPos,
+              backgroundColor: 'rgba(0,0,0,'+(0.5*relX)+')'
+            }).
+            transform('rotateY(0deg)');
+          else
+            data.ashadow.hide();
+
+          data.bshadow.css({opacity:-relX + 1});
+
+          if (half) {
+            if (data.bshadow.parent()[0]!=data.wrapper[0]) {
+              data.bshadow.appendTo(data.wrapper);
+            }
+          } else {
+            if (data.bshadow.parent()[0]!=data.fpage[0]) {
+              data.bshadow.appendTo(data.fpage);
+            }
+          }
+          /*data.bshadow.css({
+            backgroundColor: 'rgba(0,0,0,'+(0.1)+')'
+          })*/
+          gradient(data.bshadow, point2D(gradientX * 100, 0), point2D((-gradientX + 1)*100, 0),
+            [[0, 'rgba(0,0,0,0.3)'],[1, 'rgba(0,0,0,0)']],2);
+          
+        }
+
+        break;
+      case 'sheet':
+
+        var that = this,
+          a = 0,
+          alpha = 0,
+          beta,
+          px,
+          gradientEndPointA,
+          gradientEndPointB,
+          gradientStartVal,
+          gradientSize,
+          gradientOpacity,
+          shadowVal,
+          mv = point2D(0, 0),
+          df = point2D(0, 0),
+          tr = point2D(0, 0),
+          folding = flipMethods._foldingPage.call(this),
+          tan = Math.tan(alpha),
+          ac = turnData.opts.acceleration,
+          h = data.wrapper.height(),
+          top = point.corner.substr(0, 1) == 't',
+          left = point.corner.substr(1, 1) == 'l',
+
+      compute = function() {
+
+        var rel = point2D(0, 0);
+        var middle = point2D(0, 0);
+
+        rel.x = (o.x) ? o.x - point.x : point.x;
+
+        if (!hasRot) {
+          rel.y = 0;
+        } else {
+          rel.y = (o.y) ? o.y - point.y : point.y;
+        }
+
+        middle.x = (left)? width - rel.x/2 : point.x + rel.x/2;
+        middle.y = rel.y/2;
+        
+        var alpha =  A90-Math.atan2(rel.y, rel.x),
+          gamma = alpha - Math.atan2(middle.y, middle.x),
+          distance =  Math.max(0, Math.sin(gamma) * Math.sqrt(Math.pow(middle.x, 2) + Math.pow(middle.y, 2)));
+          
+          a = deg(alpha);
+
+          tr = point2D(distance * Math.sin(alpha), distance * Math.cos(alpha));
+
+          if (alpha > A90) {
+            tr.x = tr.x + Math.abs(tr.y * rel.y/rel.x);
+            tr.y = 0;
+            if (Math.round(tr.x*Math.tan(PI-alpha)) < height) {
+              point.y = Math.sqrt(Math.pow(height, 2)+2 * middle.x * rel.x);
+              if (top) point.y =  height - point.y;
+              return compute();
+            }
+          }
+      
+          if (alpha>A90) {
+            var beta = PI-alpha, dd = h - height/Math.sin(beta);
+            mv = point2D(Math.round(dd*Math.cos(beta)), Math.round(dd*Math.sin(beta)));
+            if (left) mv.x = - mv.x;
+            if (top) mv.y = - mv.y;
+          }
+
+          px = Math.round(tr.y/Math.tan(alpha) + tr.x);
+      
+          var side = width - px,
+            sideX = side*Math.cos(alpha*2),
+            sideY = side*Math.sin(alpha*2);
+            df = point2D(
+              Math.round((left ? side -sideX : px+sideX)),
+              Math.round((top) ? sideY : height - sideY));
+          
+        // Gradients
+          if (turnData.opts.gradients) {
+
+            gradientSize = side*Math.sin(alpha);
+
+            var endingPoint = flipMethods._c2.call(that, point.corner),
+             far = Math.sqrt(Math.pow(endingPoint.x-point.x, 2)+Math.pow(endingPoint.y-point.y, 2))/width;
+
+            shadowVal = Math.sin(A90*((far>1) ? 2 - far : far));
+
+            gradientOpacity = Math.min(far, 1);
+
+           
+              gradientStartVal = gradientSize>100 ? (gradientSize-100)/gradientSize : 0;
+
+              gradientEndPointA = point2D(
+                gradientSize*Math.sin(alpha)/width*100,
+                gradientSize*Math.cos(alpha)/height*100);
+           
+
+              if (flipMethods._backGradient.call(that)) {
+
+                gradientEndPointB = point2D(
+                  gradientSize*1.2*Math.sin(alpha)/width*100,
+                  gradientSize*1.2*Math.cos(alpha)/height*100);
+
+                if (!left) gradientEndPointB.x = 100-gradientEndPointB.x;
+                if (!top) gradientEndPointB.y = 100-gradientEndPointB.y;
+
+              }
+
+          }
+
+          tr.x = Math.round(tr.x);
+          tr.y = Math.round(tr.y);
+
+        return true;
+      },
+
+      transform = function(tr, c, x, a) {
+      
+        var f = ['0', 'auto'], mvW = (width-h)*x[0]/100, mvH = (height-h)*x[1]/100,
+          cssA = {left: f[c[0]], top: f[c[1]], right: f[c[2]], bottom: f[c[3]]},
+          cssB = {},
+          aliasingFk = (a!=90 && a!=-90) ? (left ? -1 : 1) : 0,
+          origin = x[0] + '% ' + x[1] + '%';
+
+        that.css(cssA).
+          transform(rotate(a) + translate(tr.x + aliasingFk, tr.y, ac), origin);
+
+        data.fpage.css(cssA).transform(
+          rotate(a) +
+          translate(tr.x + df.x - mv.x - width*x[0]/100, tr.y + df.y - mv.y - height*x[1]/100, ac) +
+          rotate((180/a - 2)*a),
+          origin);
+
+        data.wrapper.transform(translate(-tr.x + mvW-aliasingFk, -tr.y + mvH, ac) + rotate(-a), origin);
+
+        data.fwrapper.transform(translate(-tr.x + mv.x + mvW, -tr.y + mv.y + mvH, ac) + rotate(-a), origin);
+      
+        if (turnData.opts.gradients) {
+
+          if (x[0])
+            gradientEndPointA.x = 100-gradientEndPointA.x;
+
+          if (x[1])
+            gradientEndPointA.y = (100-gradientEndPointA.y);
+
+          cssB['box-shadow'] = '0 0 20px rgba(0,0,0,'+(0.5*shadowVal)+')';
+          folding.css(cssB);
+
+          gradient(data.ashadow,
+              point2D(left?100:0, top?0:100),
+              point2D(gradientEndPointA.x, gradientEndPointA.y),
+              [[gradientStartVal, 'rgba(0,0,0,0)'],
+              [((1-gradientStartVal)*0.8)+gradientStartVal, 'rgba(0,0,0,'+(0.2*gradientOpacity)+')'],
+              [1, 'rgba(255,255,255,'+(0.2*gradientOpacity)+')']],
+              3,
+              alpha);
+
+          if (flipMethods._backGradient.call(that))
+            gradient(data.bshadow,
+                point2D(left?0:100, top?0:100),
+                point2D(gradientEndPointB.x, gradientEndPointB.y),
+                [[0.6, 'rgba(0,0,0,0)'],
+                [0.8, 'rgba(0,0,0,'+(0.3*gradientOpacity)+')'],
+                [1, 'rgba(0,0,0,0)']
+                ],
+                3);
+        }
+
+      };
+
+      switch (point.corner) {
+        case 'l' :
+
+
+        break;
+        case 'r' :
+
+
+        break;
+        case 'tl' :
+          point.x = Math.max(point.x, 1);
+          compute();
+          transform(tr, [1,0,0,1], [100, 0], a);
+        break;
+        case 'tr' :
+          point.x = Math.min(point.x, width-1);
+          compute();
+          transform(point2D(-tr.x, tr.y), [0,0,0,1], [0, 0], -a);
+        break;
+        case 'bl' :
+          point.x = Math.max(point.x, 1);
+          compute();
+          transform(point2D(tr.x, -tr.y), [1,1,0,0], [100, 100], -a);
+        break;
+        case 'br' :
+          point.x = Math.min(point.x, width-1);
+          compute();
+          transform(point2D(-tr.x, -tr.y), [0,1,1,0], [0, 100], a);
+        break;
+      }
+
+    break;
+  }
+
+    data.point = point;
+  
+  },
+
+  _moveFoldingPage: function(move) {
+
+    var data = this.data().f;
+
+    if (!data)
+      return;
+
+    var turn = data.opts.turn,
+      turnData = turn.data(),
+      place = turnData.pagePlace;
+      
+    if (move) {
+
+      var nextPage = data.opts.next;
+  
+      if (place[nextPage]!=data.opts.page) {
+
+        if (data.folding)
+          flipMethods._moveFoldingPage.call(this, false);
+
+        var folding = flipMethods._foldingPage.call(this);
+        
+        folding.appendTo(data.fpage);
+        place[nextPage] = data.opts.page;
+        data.folding = nextPage;
+      }
+
+      turn.turn('update');
+
+    } else {
+
+      if (data.folding) {
+
+        if (turnData.pages[data.folding]) {
+         
+          // If we have flip available
+
+          var flipData = turnData.pages[data.folding].data().f;
+          
+          turnData.pageObjs[data.folding].
+            appendTo(flipData.wrapper);
+
+        } else if (turnData.pageWrap[data.folding]) {
+          
+          // If we have the pageWrapper
+
+          turnData.pageObjs[data.folding].
+            appendTo(turnData.pageWrap[data.folding]);
+
+        }
+
+        if (data.folding in place) {
+          place[data.folding] = data.folding;
+        }
+
+        delete data.folding;
+
+      }
+    }
+  },
+
+  _showFoldedPage: function(c, animate) {
+
+    var folding = flipMethods._foldingPage.call(this),
+      dd = this.data(),
+      data = dd.f,
+      visible = data.visible;
+
+    if (folding) {
+
+      if (!visible || !data.point || data.point.corner!=c.corner) {
+
+        var corner = (
+          data.status=='hover' ||
+          data.status=='peel' ||
+          data.opts.turn.data().mouseAction) ?
+        c.corner : null;
+
+        visible = false;
+
+        if (trigger('start', this, [data.opts, corner])=='prevented')
+          return false;
+
+      }
+
+      if (animate) {
+        
+        var that = this,
+          point = (data.point && data.point.corner==c.corner) ?
+          data.point : flipMethods._c.call(this, c.corner, 1);
+      
+        this.animatef({
+          from: [point.x, point.y],
+          to: [c.x, c.y],
+          duration: 500,
+          frame: function(v) {
+            c.x = Math.round(v[0]);
+            c.y = Math.round(v[1]);
+            flipMethods._fold.call(that, c);
+          }
+        });
+
+      } else  {
+
+        flipMethods._fold.call(this, c);
+
+        if (dd.effect && !dd.effect.turning)
+          this.animatef(false);
+
+      }
+
+      if (!visible) {
+
+        switch(data.effect) {
+          case 'hard':
+
+            data.visible = true;
+            flipMethods._moveFoldingPage.call(this, true);
+            data.fpage.show();
+            if (data.opts.shadows)
+              data.bshadow.show();
+
+          break;
+          case 'sheet':
+
+            data.visible = true;
+            data.fparent.show().data().flips++;
+            flipMethods._moveFoldingPage.call(this, true);
+            data.fwrapper.show();
+            if (data.bshadow)
+              data.bshadow.show();
+
+          break;
+        }
+
+      }
+
+      return true;
+
+    }
+
+    return false;
+  },
+
+  hide: function() {
+
+    var data = this.data().f,
+      turnData = data.opts.turn.data(),
+      folding = flipMethods._foldingPage.call(this);
+
+    switch (data.effect) {
+      case 'hard':
+        
+        if (turnData.opts.gradients) {
+          data.bshadowLoc = 0;
+          data.bshadow.remove();
+          data.ashadow.hide();
+        }
+
+        data.wrapper.transform('');
+        data.fpage.hide();
+
+      break;
+      case 'sheet':
+
+        if ((--data.fparent.data().flips)===0)
+          data.fparent.hide();
+
+        this.css({left: 0, top: 0, right: 'auto', bottom: 'auto'}).
+          transform('');
+
+        data.wrapper.transform('');
+
+        data.fwrapper.hide();
+
+        if (data.bshadow)
+          data.bshadow.hide();
+
+        folding.transform('');
+
+      break;
+    }
+
+      data.visible = false;
+
+    return this;
+  },
+
+  hideFoldedPage: function(animate) {
+
+    var data = this.data().f;
+
+    if (!data.point) return;
+
+    var that = this,
+      p1 = data.point,
+      hide = function() {
+        data.point = null;
+        data.status = '';
+        that.flip('hide');
+        that.trigger('end', [data.opts, false]);
+      };
+
+    if (animate) {
+
+      var p4 = flipMethods._c.call(this, p1.corner),
+        top = (p1.corner.substr(0,1)=='t'),
+        delta = (top) ? Math.min(0, p1.y-p4.y)/2 : Math.max(0, p1.y-p4.y)/2,
+        p2 = point2D(p1.x, p1.y+delta),
+        p3 = point2D(p4.x, p4.y-delta);
+    
+      this.animatef({
+        from: 0,
+        to: 1,
+        frame: function(v) {
+          var np = bezier(p1, p2, p3, p4, v);
+          p1.x = np.x;
+          p1.y = np.y;
+          flipMethods._fold.call(that, p1);
+        },
+        complete: hide,
+        duration: 800,
+        hiding: true
+      });
+
+    } else {
+
+      this.animatef(false);
+      hide();
+
+    }
+  },
+
+  turnPage: function(corner) {
+
+    var that = this,
+      data = this.data().f,
+      turnData = data.opts.turn.data();
+
+    corner = {corner: (data.corner) ?
+      data.corner.corner :
+      corner || flipMethods._cAllowed.call(this)[0]};
+
+    var p1 = data.point ||
+      flipMethods._c.call(this,
+        corner.corner,
+        (data.opts.turn) ? turnData.opts.elevation : 0),
+      p4 = flipMethods._c2.call(this, corner.corner);
+
+      this.trigger('flip').
+        animatef({
+          from: 0,
+          to: 1,
+          frame: function(v) {
+
+            var np = bezier(p1, p1, p4, p4, v);
+            corner.x = np.x;
+            corner.y = np.y;
+            flipMethods._showFoldedPage.call(that, corner);
+
+          },
+          complete: function() {
+            
+            that.trigger('end', [data.opts, true]);
+
+          },
+          duration: turnData.opts.duration,
+          turning: true
+        });
+
+      data.corner = null;
+  },
+
+  moving: function() {
+
+    return 'effect' in this.data();
+  
+  },
+
+  isTurning: function() {
+
+    return this.flip('moving') && this.data().effect.turning;
+  
+  },
+
+  corner: function() {
+    
+    return this.data().f.corner;
+      
+  },
+
+  _eventStart: function(e) {
+
+    var data = this.data().f,
+      turn = data.opts.turn;
+
+    if (!data.corner && !data.disabled && !this.flip('isTurning') &&
+      data.opts.page==turn.data().pagePlace[data.opts.page])
+    {
+
+      data.corner = flipMethods._isIArea.call(this, e);
+
+      if (data.corner && flipMethods._foldingPage.call(this)) {
+
+        this.trigger('pressed', [data.point]);
+        flipMethods._showFoldedPage.call(this, data.corner);
+
+        return false;
+
+      } else
+        data.corner = null;
+
+    }
+
+  },
+
+  _eventMove: function(e) {
+
+    var data = this.data().f;
+
+    if (!data.disabled) {
+
+      e = (isTouch) ? e.originalEvent.touches : [e];
+
+      if (data.corner) {
+
+        var pos = data.parent.offset();
+        data.corner.x = e[0].pageX-pos.left;
+        data.corner.y = e[0].pageY-pos.top;
+        flipMethods._showFoldedPage.call(this, data.corner);
+
+      } else if (data.hover && !this.data().effect && this.is(':visible')) {
+
+        var point = flipMethods._isIArea.call(this, e[0]);
+
+        if (point) {
+
+          if ((data.effect=='sheet' && point.corner.length==2)  || data.effect=='hard') {
+            data.status = 'hover';
+            var origin = flipMethods._c.call(this, point.corner, data.opts.cornerSize/2);
+            point.x = origin.x;
+            point.y = origin.y;
+            flipMethods._showFoldedPage.call(this, point, true);
+          }
+        
+        } else {
+          
+          if (data.status=='hover') {
+            data.status = '';
+            flipMethods.hideFoldedPage.call(this, true);
+          }
+
+        }
+
+      }
+
+    }
+
+  },
+
+  _eventEnd: function() {
+
+    var data = this.data().f,
+      corner = data.corner;
+
+    if (!data.disabled && corner) {
+      if (trigger('released', this, [data.point || corner])!='prevented') {
+        flipMethods.hideFoldedPage.call(this, true);
+      }
+    }
+
+    data.corner = null;
+
+  },
+
+  disable: function(disable) {
+
+    flipMethods.setData.call(this, {'disabled': disable});
+    return this;
+
+  },
+
+  hover: function(hover) {
+    
+    flipMethods.setData.call(this, {'hover': hover});
+    return this;
+
+  },
+
+  peel: function (corner, animate) {
+
+    var data = this.data().f;
+
+    if (corner) {
+
+      if ($.inArray(corner, corners.all)==-1)
+        throw turnError('Corner '+corner+' is not permitted');
+
+      if ($.inArray(corner, flipMethods._cAllowed.call(this))!=-1) {
+
+        var point = flipMethods._c.call(this, corner, data.opts.cornerSize/2);
+        
+        data.status = 'peel';
+
+        flipMethods._showFoldedPage.call(this,
+        {
+          corner: corner,
+          x: point.x,
+          y: point.y
+        }, animate);
+
+      }
+
+
+    } else {
+
+      data.status = '';
+
+      flipMethods.hideFoldedPage.call(this, animate);
+      
+    }
+
+    return this;
+  }
+};
+
+
+// Processes classes
+
+function dec(that, methods, args) {
+
+  if (!args[0] || typeof(args[0])=='object')
+    return methods.init.apply(that, args);
+
+  else if (methods[args[0]])
+    return methods[args[0]].apply(that, Array.prototype.slice.call(args, 1));
+
+  else
+    throw turnError(args[0] + ' is not a method or property');
+
+}
+
+
+// Attributes for a layer
+
+function divAtt(top, left, zIndex, overf) {
+    
+  return {'css': {
+    position: 'absolute',
+    top: top,
+    left: left,
+    'overflow': overf || 'hidden',
+    zIndex: zIndex || 'auto'
+  }
+};
+      
+}
+
+// Gets a 2D point from a bezier curve of four points
+
+function bezier(p1, p2, p3, p4, t) {
+
+  var a = 1 - t,
+    b = a * a * a,
+    c = t * t * t;
+    
+  return point2D(Math.round(b*p1.x + 3*t*a*a*p2.x + 3*t*t*a*p3.x + c*p4.x),
+    Math.round(b*p1.y + 3*t*a*a*p2.y + 3*t*t*a*p3.y + c*p4.y));
+
+}
+  
+// Converts an angle from degrees to radians
+
+function rad(degrees) {
+  
+  return degrees/180*PI;
+
+}
+
+// Converts an angle from radians to degrees
+
+function deg(radians) {
+  
+  return radians/PI*180;
+
+}
+
+// Gets a 2D point
+
+function point2D(x, y) {
+  
+  return {x: x, y: y};
+
+}
+
+// Webkit 534.3 on Android wrongly repaints elements that use overflow:hidden + rotation
+
+function rotationAvailable() {
+  var parts;
+
+  if ((parts = /AppleWebkit\/([0-9\.]+)/i.exec(navigator.userAgent))) {
+    var webkitVersion = parseFloat(parts[1]);
+    return (webkitVersion>534.3);
+  } else {
+    return true;
+  }
+}
+
+// Returns the traslate value
+
+function translate(x, y, use3d) {
+  
+  return (has3d && use3d) ? ' translate3d(' + x + 'px,' + y + 'px, 0px) '
+  : ' translate(' + x + 'px, ' + y + 'px) ';
+
+}
+
+// Returns the rotation value
+
+function rotate(degrees) {
+  
+  return ' rotate(' + degrees + 'deg) ';
+
+}
+
+// Checks if a property belongs to an object
+
+function has(property, object) {
+  
+  return Object.prototype.hasOwnProperty.call(object, property);
+
+}
+
+// Gets the CSS3 vendor prefix
+
+function getPrefix() {
+
+  var vendorPrefixes = ['Moz','Webkit','Khtml','O','ms'],
+  len = vendorPrefixes.length,
+  vendor = '';
+
+  while (len--)
+    if ((vendorPrefixes[len] + 'Transform') in document.body.style)
+      vendor='-'+vendorPrefixes[len].toLowerCase()+'-';
+
+  return vendor;
+
+}
+
+// Detects the transitionEnd Event
+
+function getTransitionEnd() {
+
+  var t,
+    el = document.createElement('fakeelement'),
+    transitions = {
+      'transition':'transitionend',
+      'OTransition':'oTransitionEnd',
+      'MSTransition':'transitionend',
+      'MozTransition':'transitionend',
+      'WebkitTransition':'webkitTransitionEnd'
+    };
+
+  for (t in transitions) {
+    if (el.style[t] !== undefined) {
+      return transitions[t];
+    }
+  }
+}
+
+// Gradients
+
+function gradient(obj, p0, p1, colors, numColors) {
+
+  var j, cols = [];
+
+  if (vendor=='-webkit-') {
+
+    for (j = 0; j<numColors; j++)
+      cols.push('color-stop('+colors[j][0]+', '+colors[j][1]+')');
+    
+    obj.css({'background-image':
+        '-webkit-gradient(linear, '+
+        p0.x+'% '+
+        p0.y+'%,'+
+        p1.x+'% '+
+        p1.y+'%, '+
+        cols.join(',') + ' )'});
+  } else {
+    
+    p0 = {x:p0.x/100 * obj.width(), y:p0.y/100 * obj.height()};
+    p1 = {x:p1.x/100 * obj.width(), y:p1.y/100 * obj.height()};
+
+    var dx = p1.x-p0.x,
+      dy = p1.y-p0.y,
+      angle = Math.atan2(dy, dx),
+      angle2 = angle - Math.PI/2,
+      diagonal = Math.abs(obj.width()*Math.sin(angle2))+Math.abs(obj.height()*Math.cos(angle2)),
+      gradientDiagonal = Math.sqrt(dy*dy + dx*dx),
+      corner = point2D((p1.x<p0.x) ? obj.width() : 0, (p1.y<p0.y) ? obj.height() : 0),
+      slope = Math.tan(angle),
+      inverse = -1/slope,
+      x = (inverse*corner.x - corner.y - slope*p0.x + p0.y)/(inverse-slope),
+      c = {x: x, y: inverse*x - inverse*corner.x + corner.y},
+      segA = (Math.sqrt( Math.pow(c.x-p0.x,2) + Math.pow(c.y-p0.y,2)));
+
+      for (j = 0; j<numColors; j++)
+        cols.push(' '+colors[j][1]+' '+((segA + gradientDiagonal*colors[j][0])*100/diagonal)+'%');
+
+      obj.css({'background-image': vendor+'linear-gradient(' + (-angle) + 'rad,' + cols.join(',') + ')'});
+  }
+}
+
+
+// Triggers an event
+
+function trigger(eventName, context, args) {
+
+  var event = $.Event(eventName);
+  context.trigger(event, args);
+  if (event.isDefaultPrevented())
+    return 'prevented';
+  else if (event.isPropagationStopped())
+    return 'stopped';
+  else
+    return '';
+}
+
+// JS Errors
+
+function turnError(message) {
+
+  function TurnJsError(message) {
+    this.name = "TurnJsError";
+    this.message = message;
+  }
+
+  TurnJsError.prototype = new Error();
+  TurnJsError.prototype.constructor = TurnJsError;
+  return new TurnJsError(message);
+
+}
+
+// Find the offset of an element ignoring its transformation
+
+function findPos(obj) {
+
+  var offset = {top: 0, left: 0};
+
+  do{
+    offset.left += obj.offsetLeft;
+    offset.top += obj.offsetTop;
+  } while ((obj = obj.offsetParent));
+
+  return offset;
+
+}
+
+// Checks if there's hard page compatibility
+// IE9 is the only browser that does not support hard pages
+
+function hasHardPage() {
+  return (navigator.userAgent.indexOf('MSIE 9.0')==-1);
+}
+
+// Request an animation
+
+window.requestAnim = (function() {
+  return window.requestAnimationFrame ||
+    window.webkitRequestAnimationFrame ||
+    window.mozRequestAnimationFrame ||
+    window.oRequestAnimationFrame ||
+    window.msRequestAnimationFrame ||
+    function(callback) {
+      window.setTimeout(callback, 1000 / 60);
+    };
+
+})();
+
+// Extend $.fn
+
+$.extend($.fn, {
+
+  flip: function() {
+    return dec($(this[0]), flipMethods, arguments);
+  },
+
+  turn: function() {
+    return dec($(this[0]), turnMethods, arguments);
+  },
+
+  transform: function(transform, origin) {
+
+    var properties = {};
+    
+    if (origin)
+      properties[vendor+'transform-origin'] = origin;
+    
+    properties[vendor+'transform'] = transform;
+  
+    return this.css(properties);
+
+  },
+
+  animatef: function(point) {
+
+    var data = this.data();
+
+    if (data.effect)
+      data.effect.stop();
+
+    if (point) {
+
+      if (!point.to.length) point.to = [point.to];
+      if (!point.from.length) point.from = [point.from];
+
+      var diff = [],
+        len = point.to.length,
+        animating = true,
+        that = this,
+        time = (new Date()).getTime(),
+        frame = function() {
+
+          if (!data.effect || !animating)
+            return;
+
+          var v = [],
+            timeDiff = Math.min(point.duration, (new Date()).getTime() - time);
+
+          for (var i = 0; i < len; i++)
+            v.push(data.effect.easing(1, timeDiff, point.from[i], diff[i], point.duration));
+
+          point.frame((len==1) ? v[0] : v);
+
+          if (timeDiff==point.duration) {
+            delete data['effect'];
+            that.data(data);
+            if (point.complete)
+              point.complete();
+          } else {
+            window.requestAnim(frame);
+          }
+        };
+
+      for (var i = 0; i < len; i++)
+        diff.push(point.to[i] - point.from[i]);
+
+      data.effect = $.extend({
+        stop: function() {
+          animating = false;
+        },
+        easing: function (x, t, b, c, data) {
+          return c * Math.sqrt(1 - (t=t/data-1)*t) + b;
+        }
+      }, point);
+
+      this.data(data);
+
+      frame();
+
+    } else {
+      
+      delete data['effect'];
+
+    }
+  }
+});
+
+// Export some globals
+
+$.isTouch = isTouch;
+$.mouseEvents = mouseEvents;
+$.cssPrefix = getPrefix;
+$.cssTransitionEnd = getTransitionEnd;
+$.findPos = findPos;
+
+})(jQuery);

+ 130 - 80
src/views/analysisReport/components/dCharts/barChart.vue

@@ -4,7 +4,7 @@
     </div>
 </template>
 <script>
-import throttle from 'lodash/throttle';
+import _ from 'lodash'
 import * as echarts from 'echarts'; 
 
   export default{
@@ -72,9 +72,13 @@ import * as echarts from 'echarts';
       },//标题
 
       average:{
-        type:[String, Number],
+        type:[String, Number,Array],
         default:'0'
       },//平均分辅助线
+      markLineData:{
+        type:Array,
+        default:()=>[]
+      },//平均分辅助线
       isShowMarkLine:{
         type:Boolean,
         default:true
@@ -101,15 +105,56 @@ import * as echarts from 'echarts';
         type:[String,Number],
         default:''
       },//满分
+      gridLeft:{
+        type:Number,
+        default:40
+      },
+      gridRight:{
+        type:Number,
+        default:60
+      },
+      gridTop:{
+        type:Number,
+        default:20
+      },
+      fontSize:{//字体大小
+        type:[Number,String],
+        default:''
+      },
+      fontColor:{
+        type:String,
+        default:''
+      },
+      showDataZoom:{//是否显示滚动条
+        type:Boolean,
+        default:true
+      },
+      barMaxWidth:{
+        type:Number,
+        default:50
+      },
+      barMinWidth:{
+        type:Number,
+        default:20
+      }
     },
     watch:{
         datay:{
             handler:'LoadEchart',
             deep:true,
         },//数值变化时重新加载图表
+        markLineData:{
+            handler:'LoadEchart',
+            deep:true,
+        },//数值变化时重新加载图表
+    },
+    computed:{
+        getCompareAnalysis(){
+            return this.$global.getCompareAnalysis()
+        },
     },
     created(){
-        // window.addEventListener('resize', this.handleResize);//创建时增加监听窗口变化事件
+        window.addEventListener('resize', this.handleResize);//创建时增加监听窗口变化事件
     },
     beforeDestroy() {
         // 组件销毁移除监听 防止内存泄漏
@@ -131,7 +176,7 @@ import * as echarts from 'echarts';
 
             const colors = [];
             const xColors=[];
-            console.log("打印x轴数据",this.datax);
+            // console.log("打印x轴数据",this.datax);
             const rightAnswerValue = this.answerValue || '';//正确答案
             for (let i = 0; i < this.datax.length; i++) {
                 if(this.color){
@@ -144,13 +189,13 @@ import * as echarts from 'echarts';
             }
 
             // 初始化 xAxisLabels 数组
-            this.xAxisLabels = this.datax.map((label, index) => ({
-                value: label,
-                textStyle: {
-                    color: xColors[index],
-                    fontSize: 14
-                }
-            }));
+            // this.xAxisLabels = this.datax.map((label, index) => ({
+            //     value: label,
+            //     textStyle: {
+            //         color: xColors[index],
+            //         fontSize: 14
+            //     }
+            // }));
             const markPointColor=this.color;
             //devicePixelRatio: 2  2表示每个逻辑像素对应 2个物理像素。这会进一步提高图表的清晰度,适用于更高分辨率的屏幕  解决高分辨率显示器文字模糊的问题
             this.echart=echarts.init(this.$refs.bar_echart,{devicePixelRatio: 2});//获取echarts容器  如果组件同一个页面多处使用 这里不能用id获取实例
@@ -166,14 +211,39 @@ import * as echarts from 'echarts';
             // console.log("打印datay", this.datay);
 
             //计算最大值和最小值
-            const datay = this.datay.filter(num => !isNaN(num));//// 过滤掉NaN值
+            const datay = this.datay.filter(num => !isNaN(num))
             const maxValue=Math.max(...datay);
             const minValue=Math.min(...datay);
-            const average=parseFloat(this.average) || 0;
+            let average = 0, markLineData = [],maxAverage = [];
+            if(Object.prototype.toString.call(this.average) === '[object Array]'){
+                average = this.average;
+                this.average.forEach(item=>{
+                    const itemAverage = parseFloat(item) || 0
+                    if(item > 0) {
+                        markLineData.push({symbol:'circle',type: 'value', name: '', yAxis: itemAverage})
+                    }
+                })
+                maxAverage = average.length ? Math.max(...average) : 0;
+            }else if(this.markLineData && this.markLineData.length){
+                average = [];
+                this.markLineData.forEach((item,index)=>{
+                    const itemAverage = parseFloat(item.value) || 0
+                    average.push(itemAverage)
+                    if(item.isShow){
+                        markLineData.push({symbol:'circle',type: 'value', name: '', yAxis: itemAverage,lineStyle: {color: this.getCompareAnalysis[index]},label:{color:this.getCompareAnalysis[index]}})
+                    }
+                })
+                maxAverage = average.length ? Math.max(...average) : 0;
+            }else{
+                average=parseFloat(this.average) || 0;
+                maxAverage = parseFloat(this.average) || 0;
+                markLineData = average!=0?[{symbol:'circle',type: 'value', name: '', yAxis: average}]:[];
+            }
+            
+            // console.log("打印最大值", maxValue);
             // console.log("打印最小值", minValue);
             // console.log("打印平均分", average);
             const nearestValue = this.GetMaxValue(maxValue);//向上取整为最接近10的数字
-
             // console.log("打印最大值的",nearestValue)
             let yAxisUnit=this.showNuitY && this.unit == '%' ? this.unit : '';
             let splitArea=this.showSplitArea?{ "show": true, areaStyle: {"color": ["#fafafa", "#ffffff"]}}:{}
@@ -267,13 +337,13 @@ import * as echarts from 'echarts';
                 // },
                
                 grid: {
-                    left: 40,
-                    right: 20,
-                    top: 20,
+                    left: this.gridLeft,
+                    right: this.gridRight,
+                    top: this.gridTop,
                     bottom: 0,
                     containLabel: true, //用于控制网格区域是否包含坐标轴的标签  true 可以确保所有标签都能完整显示,不会被裁剪。
                 },
-                dataZoom: singleSeriesWidth < barMinWidth ? dataZoom : null,
+                dataZoom: this.showDataZoom?(singleSeriesWidth < barMinWidth ? dataZoom : null):null,
                 xAxis: [
                     {
                         type: 'category',
@@ -294,7 +364,7 @@ import * as echarts from 'echarts';
                                 // console.log("文字宽度",valueWidth);
                                 // console.log("单系列宽度",singleSeriesWidth);
                                 if(valueWidth > singleSeriesWidth) {
-                                    if (singleSeriesWidth < 80) {
+                                    if (singleSeriesWidth < 100) {
                                         if(valueWidth > 80) {
                                             // let maxLength = Math.floor(80 / 14) - 1;
                                             return value.slice(0, 2) + '...' + value.slice(-3)
@@ -309,12 +379,12 @@ import * as echarts from 'echarts';
                                     return value;
                                 }
                             },
-                            color: '#666666',//y轴文字颜色
+                            color: this.fontColor || '#666666',//y轴文字颜色
                             // width: 80, // 控制每个标签最大宽度,超出则截断
                             // overflow: 'truncate', // 超出宽度显示省略号
                             // ellipsis: '...', // 自定义省略符号
                             // margin: 20,
-                            fontSize: 14,
+                            fontSize: this.fontSize?this.fontSize:14,
                             // rich: {
                             //     // 定义省略样式(可选)
                             //     text: {
@@ -335,11 +405,11 @@ import * as echarts from 'echarts';
                     type: 'value',
                     // name: '进度(%)',
                     axisLabel: {
-                        color: '#666666',//y轴文字颜色
-                        fontSize: 14,
+                        color: this.fontColor || '#666666',//y轴文字颜色
+                        fontSize: this.fontSize?this.fontSize:14,
                         formatter: '{value}'+yAxisUnit, // 添加 % 符号
                     },
-                    max: average > nearestValue ? average : nearestValue,//固定y轴最高值为最大值的向上取整
+                    max: maxAverage > nearestValue ? maxAverage : nearestValue,//固定y轴最高值为最大值的向上取整
                     // splitNumber: 10, // 设置刻度线分段数
                     // interval: 0, // 确保所有刻度标签都显示
                     // splitLine: {
@@ -371,8 +441,8 @@ import * as echarts from 'echarts';
                             color: 'red'
                         },//柱子的背景色
                         // barMinHeight:this.isClick?30:0,//柱子最小高度
-                        barMaxWidth: 50, // 设置柱状图的最大宽度
-                        barMinWidth: 20,
+                        barMaxWidth: this.barMaxWidth, // 设置柱状图的最大宽度
+                        barMinWidth: this.barMinWidth,
                         // barWidth: '44px', // 设置柱状图的宽度,可以是百分比或具体数值
                         itemStyle: {
                             color: function(params) {
@@ -422,16 +492,16 @@ import * as echarts from 'echarts';
                         data: this.datay.map(value => ({
                             value: value,
                             label: {
-                                // show: this.showMarkPoint ? (value === 0 || value === maxValue || value === minValue) : singleSeriesWidth > 22,//数值为0不显示
+                                show: this.showMarkPoint ? (value === 0 || value === maxValue || value === minValue) : singleSeriesWidth > 26,//数值为0不显示
                                 position: "top",
-                                fontSize: 14,
+                                fontSize: this.fontSize?this.fontSize:14,
                                 formatter: '{c}'+this.unit, // 显示的格式
                                 color:"#666"
                                 // color: (value == maxValue)? "#3BA272": (value == minValue)? "#EE6666": "#666"
                             },
                         })),
                         markLine: {
-                            symbol:average==0?['none', 'none']:['circle','arrow'],//设置标记线样式  圆形'circle', 矩形'rect', 圆角矩形'roundRect', 三角形'triangle', 菱形'diamond', 图钉行'pin', 箭头行'arrow', 'none'
+                            // symbol:average==0?['none', 'none']:['circle','arrow'],//设置标记线样式  圆形'circle', 矩形'rect', 圆角矩形'roundRect', 三角形'triangle', 菱形'diamond', 图钉行'pin', 箭头行'arrow', 'none'
                             symbolSize:[8,8],//设置标记线样式大小 宽高
                             symbolOffset: [
                                 [0, 0],    // 起始标记偏移
@@ -440,7 +510,7 @@ import * as echarts from 'echarts';
                             label:{
                                 // formatter: '{b}: {c}',
                                 color:'#F56C6C',
-                                fontSize: 15,
+                                fontSize: this.fontSize?this.fontSize:15,
                                 formatter: '{c}'+this.unit,
                                 position: 'end',//'start', 'middle', 'end', 'insideStartTop', 'insideStartBottom', 'insideMiddleTop', 'insideMiddleBottom', 'insideEndTop', 'insideEndBottom'
                                 // distance:[-10,-40],//标签与线之间的间距。如果是数组,第一项为横向间距,第二项为纵向间距。如果是数字,则表示横向纵向使用相同的间距
@@ -448,14 +518,8 @@ import * as echarts from 'echarts';
                             tooltip: {
                                formatter:{},
                             },
-                            data: average!=0?[
-                                { 
-                                    type: 'value', 
-                                    name: '', 
-                                    yAxis: average
-                                }
-                            ]:[],
-                            lineStyle: {
+                            data: markLineData,
+                            lineStyle: this?.markLineData?.length?null:{
                                 color: '#F56C6C'
                             },//辅助线的颜色
                         
@@ -519,6 +583,7 @@ import * as echarts from 'echarts';
             //     option.xAxis[0].axisLabel.rich[`a${index}`] = label.textStyle;
             // });
             this.echart.setOption(option);
+
             //是否可以点击
             if(this.isClick)
             {
@@ -537,27 +602,27 @@ import * as echarts from 'echarts';
                     {
                         
                         // 更新 xAxisLabels 数组
-                        this.xAxisLabels = this.datax.map((label, index) => ({
-                            value: label,
-                            textStyle: {
-                                color: index === params.dataIndex ? '#2E64FA' : xColors[index],
-                                fontSize: 14
-                            }
-                        }));
+                        // this.xAxisLabels = this.datax.map((label, index) => ({
+                        //     value: label,
+                        //     textStyle: {
+                        //         color: index === params.dataIndex ? '#2E64FA' : xColors[index],
+                        //         fontSize: 14
+                        //     }
+                        // }));
                         // 设置 rich 样式
-                        this.xAxisLabels.forEach((label, index) => {
-                            this.echart.setOption({
-                                xAxis: [
-                                    {
-                                        axisLabel: {
-                                            rich: {
-                                                [`a${index}`]: label.textStyle
-                                            }
-                                        }
-                                    }
-                                ]
-                            });
-                        });
+                        // this.xAxisLabels.forEach((label, index) => {
+                        //     this.echart.setOption({
+                        //         xAxis: [
+                        //             {
+                        //                 axisLabel: {
+                        //                     rich: {
+                        //                         [`a${index}`]: label.textStyle
+                        //                     }
+                        //                 }
+                        //             }
+                        //         ]
+                        //     });
+                        // });
                         this.echart.setOption({
                             series: [
                                 {
@@ -621,26 +686,7 @@ import * as echarts from 'echarts';
                     },
                 });
             }
-            // 获取grid区域偏移量
-            const gridRect = this.echart.getModel().getComponent('grid').coordinateSystem.getRect();
-            const dataLens = this.datax.length ?? 0;
-            const itemBarWidth = (gridRect.width / dataLens - 0) * 0.7;//每个柱子的真实宽度
-            this.echart.setOption({
-                series: {
-                    data: this.datay.map(value => ({
-                        value: value,
-                        label: {
-                            show: itemBarWidth > 23,
-                            position: "top",
-                            fontSize: 14,
-                            formatter: '{c}'+this.unit, // 显示的格式
-                            color:"#666"
-                            // color: (value == maxValue)? "#3BA272": (value == minValue)? "#EE6666": "#666"
-                        },
-                    }))
-                }
-            });
-            window.addEventListener("resize", this.handleResize); //创建时增加监听窗口变化事件
+            
         },
 
         //获取最大值  计算规则  
@@ -798,10 +844,14 @@ import * as echarts from 'echarts';
         },
 
         // 监听窗口大小变化,重新计算表格高度 使用节流防止频繁改变窗口大小导致计算量过大而页面卡顿
-        handleResize: throttle(function () {
+        handleResize: _.throttle(function() {
+            
             this.$nextTick(() => {
-                this.echart.resize();
+               
+                this.LoadEchart();
+                
             });
+            
         }, 500), // 节流 500 毫秒内最多执行一次
     }
   }

+ 36 - 12
src/views/analysisReport/components/dCharts/differenceChart.vue

@@ -63,6 +63,30 @@ export default {
         return ["#3BA272", "#EE6666"];
       },
     },
+    gridLeft:{
+      type:Number,
+      default:40
+    },
+    gridRight:{
+      type:Number,
+      default:60
+    },
+    gridTop:{
+      type:Number,
+      default:20
+    },
+    fontSize:{//字体大小
+      type:[Number,String],
+      default:''
+    },
+    fontColor:{
+      type:String,
+      default:''
+    },
+    showDataZoom:{//是否显示滚动条
+      type:Boolean,
+      default:true
+    }
   },
   watch: {
     datay: {
@@ -168,8 +192,8 @@ export default {
                 return `${this.$global.getFormatNumber(value).toString()+unit}`;
               }
             },
-            color: "#666",
-            fontSize: 14,
+            color: this.fontColor || "#666",
+            fontSize: this.fontSize?this.fontSize:14,
           },
           itemStyle: {
             color: numValue > 0 ? this.color[0] : this.color[1],
@@ -212,13 +236,13 @@ export default {
           },
         },
         grid: {
-          left: 40,
-          right: 20,
-          top: 20,
+          left: this.gridLeft,
+          right: this.gridRight,
+          top: this.gridTop,
           bottom:0,
           containLabel: true, //用于控制网格区域是否包含坐标轴的标签  true 可以确保所有标签都能完整显示,不会被裁剪。
         },
-        dataZoom: singleSeriesWidth < barMinWidth ? dataZoom : null,
+        dataZoom: this.showDataZoom?(singleSeriesWidth < barMinWidth ? dataZoom : null):null,
         xAxis: [
           {
             type: "category",
@@ -230,8 +254,8 @@ export default {
               interval: 0,
               margin: 20,
               rotate: singleSeriesWidth < 80 ? 45 : 0,
-              fontSize: 14,
-              color: "#666",
+              fontSize: this.fontSize?this.fontSize:14,
+              color: this.fontColor || "#666",
               fontWeight: 400,
               formatter:function(value) 
               { 
@@ -275,9 +299,9 @@ export default {
               // console.log("打印y轴的value",totalValue);
               return  totalValue.toString()+ unit;
             },
-            color: "#666",
+            color: this.fontColor || "#666",
             // interval: 0,
-            fontSize: 14,
+            fontSize: this.fontSize?this.fontSize:14,
           },
           max:maxValue,
           min:minValue,
@@ -310,8 +334,8 @@ export default {
               focus: "series",
             },
             barMinHeight:1,//设置柱状图的最小高度
-            barMaxWidth: 50, // 设置柱状图的最大宽度
-            barMinWidth: 20, ////设置柱状图的最小宽度
+            barMaxWidth: 50, // this.showDataZoom?50:30 设置柱状图的最大宽度
+            barMinWidth: this.showDataZoom?20:1, ////设置柱状图的最小宽度
             data: processedData,
           },
         ],

+ 46 - 22
src/views/analysisReport/components/dCharts/lineChart.vue

@@ -5,7 +5,7 @@
                 显示全部
             </el-checkbox>
         </div>
-        <div ref="line_chart" class="chart_box"></div>
+        <div ref="line_chart" class="chart_box" :style="{height:reportHeight || '400px'}"></div>
     </div>
 </template>
 <script>
@@ -32,7 +32,14 @@ export default {
             type: Number,
             default: 0,
         },//grid右间距
-
+        gridTop: {
+            type: [Number,String],
+            default: '',
+        },
+        legendWidth: {
+            type: [Number,String],
+            default: 'auto',
+        },
         showCheckBox: {
             type: Boolean,
             default: true,
@@ -106,7 +113,10 @@ export default {
             type:Boolean,
             default:false,
         },//是否显示柱状图label  默认显示
-
+        labelColor:{
+            type: String,
+            default:'',
+        },//柱状图label 颜色
         showMarkPoint:{
             type: Boolean,
             default:false,
@@ -126,8 +136,22 @@ export default {
             type:Number,
             default:0
         },
-
-
+        gridLeft:{
+            type:Number,
+            default:20
+        },
+        fontSize:{//字体大小
+            type:[Number,String],
+            default:''
+        },
+        fontColor:{
+            type:String,
+            default:''
+        },
+        reportHeight:{//报告高度
+            type:String,
+            default:''
+        }
 
     },
     watch: {
@@ -308,7 +332,7 @@ export default {
                 legend: {
                     show: this.title.length ? true : false,//是否显示图例
                     left: this.showCheckBox ? "110px" : "0px",//图例距左边距离  有显示全部的 默认110px
-               
+                    width:this.legendWidth,
                     itemGap: 20,//图例间距
                     itemHeight: 10,//图例的高度
                     itemWidth: 25,//图例的宽度
@@ -332,9 +356,9 @@ export default {
                     // icon:'emptyCircle',//标记类型 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none'
                 },
                 grid: {
-                    left: 40,
-                    right: 20,
-                    top: gridTop,
+                    left: this.gridLeft,
+                    right: this.gridRight,
+                    top: this.gridTop || gridTop,
                     bottom:0,
                     containLabel: true, //用于控制网格区域是否包含坐标轴的标签  true 可以确保所有标签都能完整显示,不会被裁剪。
                 },
@@ -362,8 +386,8 @@ export default {
                     axisLabel: {
                         interval: 0,//0 显示所有刻度
                         rotate: singleSeriesWidth < 60 ? 45 : 0,
-                        fontSize: 14,
-                        color: "#666",
+                        fontSize: this.fontSize?this.fontSize:14,
+                        color: this.fontColor || "#666",
                         fontWeight: 400,
                         formatter:function(value) {
                             
@@ -403,8 +427,8 @@ export default {
                         margin: 20,
                         formatter: '{value}' + this.unit,
                         interval: 0, // 显示所有 label
-                        fontSize: 14,
-                        color: "#666",
+                        fontSize: this.fontSize?this.fontSize:14,
+                        color: this.fontColor || "#666",
                         fontWeight: 400
                     },
                     splitNumber: 5,//固定显示5个刻度  非强制 不一定生效
@@ -430,16 +454,16 @@ export default {
                         },// 网格线设置隔行背景色
                     },
                     inverse:this.yInverse,
-                    min:this.showBackground?0: (val) => {
+                    // min:this.showBackground?0: (val) => {
 
                         
-                        if(val.min > 0 || val.min < -10) {
-                            // return Math.floor(val.min / 10) * 10
-                            return Math.floor(val.min)
-                        }else {
-                            return val.min + (val.min % 10)
-                        }
-                    },
+                    //     if(val.min > 0 || val.min < -10) {
+                    //         // return Math.floor(val.min / 10) * 10
+                    //         return Math.floor(val.min)
+                    //     }else {
+                    //         return val.min + (val.min % 10)
+                    //     }
+                    // },
                     // max:yAxisMax,//固定x轴最高值位100
                     // max: this.unit != '%' ? (val) => {
                     //     // console.log(val.max)
@@ -471,7 +495,7 @@ export default {
                     symbolSize:8,//折线点大小
                     label: { 
                         show: this.isShowLabel, //是否显示数值
-                        color: this.colors[index], 
+                        color: this.labelColor || this.colors[index], 
                         formatter: "{c}" + this.unit 
                     },
                     smooth: this.isSmooth,//设置折线平滑

+ 66 - 14
src/views/analysisReport/components/dCharts/radarCharts.vue

@@ -6,7 +6,7 @@
       </el-checkbox>
     </div>
     <!-- 雷达图 -->
-    <div ref="radarChart" class="chart_box" :style="{ height: `${height}px` }"></div>
+    <div ref="radarChart" class="chart_box" :style="{ height: reportHeight || `${height}px` }"></div>
   </div>
 </template>
 <script>
@@ -22,6 +22,10 @@ export default {
         type: Number,
         default: 400
     },//高度
+    reportHeight: {
+        type: [Number,String],
+        default: ''
+    },//高度
     showLegend: {
       type: Boolean,
       default: true
@@ -72,6 +76,22 @@ export default {
       ],
       require: true
     },
+    isClick:{
+      type:Boolean,
+      default:false
+    },//是否可以点击事件
+    showRadiusAxis:{//是否显示
+      type:Boolean,
+      default:true
+    },
+    fontSize:{
+      type:[Number,String],
+      default:''
+    },
+    fontColor:{
+      type:String,
+      default:''
+    }
   },
   components: {},
   data() {
@@ -130,16 +150,18 @@ export default {
           name: name,
           max: this.unit == '%' ? 100 : null,
           min: this.unit == '%' ? 0 : null,
-          axisLabel: index ==0 ? {
+          axisLabel: this.showRadiusAxis && index ==0 ? {
             show: true,
             formatter: (val) => `${val}${this.unit}`,
             textStyle: {
-              fontSize: 12,
+              fontSize: this.fontSize?this.fontSize:12,
               color: '#666'
             }
           } : {
             show: false
-          }
+          },
+          color:this.isClick && index == 0?'#2e64fa':(this.fontColor?this.fontColor:'#666666'),
+          nameTextStyle: { fontSize: this.fontSize?this.fontSize:14, fontWeight: this.isClick && index==0?'bold':'normal' }
         };
       });
       console.log("打印indicators数据",indicators);
@@ -263,7 +285,7 @@ export default {
           triggerOn: "mousemove | click",
           renderModel:'html',//使用html渲染模式
           confine:true,//是否将 tooltip 框限制在图表的区域内。
-          extraCssText: 'border-radius: 4px;padding:5px 0px 5px 5px;white-space:normal;word-warp:break-word;max-width: 400px;', // 设置最大宽度和高度
+          extraCssText: 'border-radius: 4px;white-space:normal;word-warp:break-word;max-width: 400px;', // 设置最大宽度和高度
           enterable:true,
           formatter: (params) => {
             const chart = this.myChart.getOption();
@@ -342,15 +364,9 @@ export default {
               color: '#999' // 设置轴线颜色
             }
           },
-
-          name: {
-            textStyle: {
-              fontSize: 14, // 设置字体大小
-              color: '#333'   // 设置字体颜色
-            }
-          },
-          center: this.showLegend?['50%', '55%']:['50%', '55%']//位置调整
+          center: this.reportHeight?['50%', '50%']:(this.showLegend?['50%', '55%']:['50%', '55%'])//位置调整
         },
+        triggerEvent: true,//坐标轴的标签是否响应和触发鼠标事件,默认不响应。
         series: [
           {
             type: "radar",
@@ -397,9 +413,45 @@ export default {
       this.myChart.setOption(option);
       console.log(option,'option')
       this.myChart.on("legendselectchanged", this.handleLegendSelectChanged);
-     
+      // 监听点击事件
+      if(this.isClick){
+        this.bindChartNameEvents(indicators);
+      }
       window.addEventListener("resize", this.resizeFn);
     },
+    //绑定点击事件
+    bindChartNameEvents(indicators){
+      if(!this.myChart){
+        return false
+      }
+      const _that = this;
+      this.myChart.on('click', function(params) {
+        // 判断是否点击的是雷达图指示器(可选:也可去掉判断,点击任何区域都隐藏)
+        if (params.componentType === 'radar' && params.targetType == 'axisName') {
+          let name = params.name;
+          const data = _that.data.slice(1);
+          const  list = data.map(item=>item[0]);
+          const index = list.indexOf(name);
+          let indicatorList = [...indicators];
+          indicatorList.forEach((item,key)=>{
+            if(key==index){
+              item.color = '#2e64fa'
+            }else{
+              item.color = '#666666'
+            }
+            item.nameTextStyle.fontWeight = key==index?'bold':'normal'
+          })
+          // 更新图表配置
+          _that.myChart.setOption({
+            radar: {
+              indicator: indicatorList,
+              
+            }
+          });
+          _that.$emit('HandleChartClick', index,name);//返回点击的柱子索引和x轴名称
+        }
+      });
+    },
     // 图例与显示全部按钮联动 事件
     handleLegendSelectChanged(params) {
       

+ 124 - 0
src/views/analysisReport/studentPage/downloadPdf/components/bookCover.vue

@@ -0,0 +1,124 @@
+<template>
+    <!-- 封面 -->
+    <div :class="['area_page', `${bookBg}`]">
+        <div class="report_cover" v-if="!isCover">
+            <div class="cover_header">
+                <p class="cover_header_title1">
+                    {{ reportTitle }}
+                </p>
+                <p class="cover_header_title1">{{ title }}</p>
+                <p class="cover_header_title2 mt15">({{ subtitle }})</p>
+            </div>
+            <div class="full_screen_button" v-if="showButton">
+                <el-button round :disabled="requestLoading" v-if="requestLoading">点击全屏查看</el-button>
+                <el-button round :loading="openLoading" v-else @click="showFullScreen">点击全屏查看</el-button>
+            </div>
+            <div class="cover_footer">
+                <p class="cover_footer_desc">报告时间:{{ reportTime }}</p>
+                <p class="cover_footer_desc mt5">报告单位:{{ userInfo.schoolName }}</p>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from "vuex";
+export default {
+    props: {
+        // 背景
+        bookBg: {
+            type: String,
+            default: () => ''
+        },
+        //标题
+        title: {
+            type: String,
+            default: () => ''
+        },
+        //副标题
+        subtitle: {
+            type: String,
+            default: () => ''
+        },
+        // 是否显示按钮
+        showButton: {
+            type: Boolean,
+            default: () => false
+        },
+        isCover:{//是否是最后一页封面
+            type: Boolean,
+            default: () => false
+        },
+        requestLoading:{
+            type:Boolean,
+            default: false
+        },//接口请求的loading
+        openLoading:{
+            type:Boolean,
+            default: false
+        },//开发全屏预览
+    },
+    data() {
+        return {
+            reportTime: this.$global.getNowDate('yyyy年mm月dd日 hh:mm:ss'),
+            titleSize:32,
+            subtitleSize:24,
+            btnWidth:210,
+            btnHeight:60,
+            btnSize:21,
+            footerSize:13
+        };
+    },
+    computed: {
+        ...mapGetters(["userInfo"]),
+        reportTitle() {
+            return this?.$store?.state?.report?.examSelectItem?.examName ?? '';
+        }
+    },
+    mounted() {
+        // 初始化时执行一次
+        // this.setFontSizeByWindowWidth();
+        // 监听窗口大小变化,实时更新
+        // window.addEventListener('resize', this.setFontSizeByWindowWidth);
+    },
+    beforeDestroy() {
+        // window.removeEventListener('resize', this.setFontSizeByWindowWidth);
+    },
+    methods: {
+        // 计算并设置字体大小的函数
+        // setFontSizeByWindowWidth() {
+        //     const windowWidth = window.innerWidth;
+        //     const windowHeigh = window.innerHeight;
+        //     const scale1 = (30 / 1920).toFixed(2);
+        //     let size1 = Math.round(windowWidth * scale1);
+
+        //     const scale2 = (24 / 1920).toFixed(2);
+        //     let size2 = Math.round(windowWidth * scale2);
+
+        //     const scale3 = (21 / 1920).toFixed(2);
+        //     let size3 = Math.round(windowWidth * scale3);
+
+        //     const btnWidthScale = (210 / 1920).toFixed(2);
+        //     let btnWidth = Math.round(windowWidth * btnWidthScale);
+        //     const btnHeightScale = (60 / 950).toFixed(2);
+        //     let btnHeight = Math.round(windowHeigh * btnHeightScale);
+
+        //     const scale4 = (13 / 1920).toFixed(2);
+        //     let size4 = Math.round(windowWidth * scale4);
+        //     // 限制字体大小范围(最小12px,最大30px)
+        //     this.titleSize = Math.max(12, Math.min(30, size1));
+        //     this.subtitleSize = Math.max(12, Math.min(24, size2));
+        //     this.btnSize = Math.max(12, Math.min(21, size3));
+        //     this.btnWidth = btnWidth;
+        //     this.btnHeight = btnHeight;
+        //     this.footerSize = Math.max(12, Math.min(13, size4));
+        // },
+        //点击进入全屏
+        showFullScreen() {
+            this.$emit('openFullScreen');
+        }
+    }
+};
+</script>
+
+<style scoped lang="scss"></style>

+ 753 - 0
src/views/analysisReport/studentPage/downloadPdf/components/bookFlip.vue

@@ -0,0 +1,753 @@
+<template>
+    <div :class="['report_page', { 'full_screen': isShowFullScreen }]" style="position: absolute;top: -9999999px;z-index: -10;">
+        <!-- 点击封面进入全屏 -->
+        <div class="joint_print_area">
+            <!-- 点击封面进入全屏预览模式 -->
+            <!-- <BookCover :bookBg="`${bookBg}_bg`" :title="coverTitle" :subtitle="subtitle" :showButton="true" :requestLoading="requestLoading" :openLoading="openLoading" @openFullScreen="openFullScreen" :style="{width:`${bookWidth}px`,height:`${bookHeight}px`,margin:'0 auto'}" v-if="!isShowFullScreen"></BookCover> -->
+            <!-- 全屏预览 -->
+            <div id="canvas" v-if="isShowFullScreen">
+                <!-- 右上角放大缩小按钮 -->
+                <div class="close-zoom-icon" @click="CloseFullScreen"><img class="close" src="@/assets/icon/close.webp" /><span>关闭全屏</span></div>
+                <div class="magazine-viewport">
+                    <div class="container">
+                        <!-- 翻书容器 -->
+                        <div class="magazine" id="flipbook" ref="bookContainer">
+                            <!-- 封面 -->
+                            <BookCover :showButton="false" :bookBg="`${bookBg}_bg`" :title="coverTitle" :subtitle="subtitle"></BookCover>
+                            <div class="area_page" v-for="(page, index) in bookPages" :key="index">
+                                <!-- 插槽:自定义每页内容 -->
+                                <slot :page="index + 1" :content="page"></slot>
+                                <!-- <div class="area_page_number">第 {{ currentPage }} 页</div> -->
+                            </div>
+                            <!-- 封面 -->
+                            <BookCover :showButton="false" :isCover="true" :bookBg="`${bookBg}_cover_bg`"></BookCover>
+                            <template v-if="bookPages.length % 2 !== 0">
+                                <BookCover :showButton="false" :isCover="true" :bookBg="`${bookBg}_cover_bg`"></BookCover>
+                            </template>
+                        </div>
+                    </div>
+                    <!-- Next button -->
+                    <div ignore="1" class="next-button"><i class="el-icon-arrow-right"></i></div>
+                    <!-- Previous button -->
+                    <div ignore="1" class="previous-button"><i class="el-icon-arrow-left"></i></div>
+                </div>
+                <!-- 翻页控制按钮 -->
+                <div class="thumbnails">
+                    <div class="book_controls">
+                        <div class="zoom">
+                            <img class="zoom_icon zoom_in_icon" src="@/assets/icon/enlarge_icon.png" />
+                            <img class="zoom_icon zoom_out_icon" src="@/assets/icon/shrink_icon.png" />
+                        </div>
+                        <div class="page_size">
+                            <span class="butn_prev" @click="prevPage"><img src="@/assets/report/turn_prev.webp" /></span>
+                            <span class="page_info">{{ currentPage }}{{ bookPages.length }}</span>
+                            <span class="butn_next" @click="nextPage"><img src="@/assets/report/turn_next.webp" /></span>
+                        </div>
+                        <el-button class="download" type="primary" :loading="loading" @click="DownloadPdf">下载报告册</el-button>
+                    </div>
+                </div>
+                <!-- <div class="page_dialog">
+                    <el-dialog title="下载PDF" class="page_dialog" :visible.sync="showReportLoading" width="500px" top="15%" append-to-body>
+                        <div class="report_loading">
+                            <div class="report_loading_icon">
+                            <img src="@/assets/report/report_loading.png">
+                            </div>
+                            <div class="report_loading_title">正在努力生成PDF中,请稍等...</div> 
+                            <div class="report_loading_progress">
+                            <el-progress :text-inside="true" color="#2E64FA" text-color="#ffffff" :stroke-width="16" :percentage="targetProgress"></el-progress>
+                            </div>
+                        </div>
+                    </el-dialog>
+                </div> -->
+            </div>
+            <!-- 下载模版 -->
+            <!-- style="position: absolute;top: -9999999px;z-index: -10;" -->
+            <div class="magazine-viewport web_mode">
+                <!-- 封面 -->
+                <BookCover class="web_cover web_area_page" :showButton="false" :bookBg="`${bookBg}_bg`" :title="coverTitle" :subtitle="subtitle"></BookCover>
+                <slot type="web_mode"></slot>
+                <!-- 封面 -->
+                <BookCover class="web_cover web_area_page" :showButton="false" :isCover="true" :bookBg="`${bookBg}_cover_bg`"></BookCover>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+// import $ from 'jquery';  // 先导入 jQuery
+// import 'turn.js'; 
+import turn from '@/utils/turn.js';
+import zoom from './js/zoom.min.js';
+import BookCover from './bookCover.vue';
+import { disableControls, addPage,largeMagazineWidth,resizeViewport,setArrows,loadLargePage,loadSmallPage } from './js/magazine.js';
+import { jsPDF } from "jspdf";
+import html2canvas from "html2canvas";
+import domtoimage from 'dom-to-image';
+import { mapGetters } from "vuex";
+export default {
+    props: {
+        // 书页数据(数组长度建议为偶数)
+        bookPages: {
+            type: Array,
+            required: true,
+            default: () => []
+        },
+        bookBg:{
+            type:String,
+            default: 'grade_leader'
+        },
+        coverTitle:{//封面标题
+            type:String,
+            default: '联校总分分析报告册'
+        },
+        subtitle:{//封面副标题
+            type:String,
+            default: '联校总分分析报告册'
+        },
+        requestLoading:{
+            type:Boolean,
+            default: false
+        },//接口请求的loading
+        openLoading:{
+            type:Boolean,
+            default: false
+        },//开发全屏预览
+        
+    },
+    components:{BookCover},
+    data() {
+        return {
+            isShowFullScreen: false,
+            bookHeight:780,//封面高度
+            bookWidth:600,//封面宽度
+            currentPage: '', // 当前页码
+            totalPages: 0, // 总页数(由 turn.js 计算)
+            showReportLoading:false,
+            targetProgress:0,
+            loading:false
+        };
+    },
+    computed: {
+        ...mapGetters(["userInfo"]),
+        reportTitle() {
+            return this?.$store?.state?.report?.examSelectItem?.examName ?? '';
+        }
+    },
+    mounted() {
+        //设置书本宽度和高度
+        this.setBookSize();
+        // 监听窗口大小变化,实时更新
+        window.addEventListener('resize', this.setBookSize);
+    },
+    beforeDestroy() {
+        // 销毁翻书实例,避免内存泄漏
+        if (this.$refs.bookContainer) {
+            $(this.$refs.bookContainer).turn('destroy');
+        }
+        window.removeEventListener('resize', this.setBookSize);
+    },
+    methods: {
+        //设置书本宽度和高度
+        setBookSize(){
+            const bookHeight = $(window).height() - 120;//书本高度
+            this.bookHeight = bookHeight;
+            this.bookWidth = Math.round(bookHeight * 0.706);// 书本宽度 0.706 宽高的比例
+        },
+        //点击查看全屏
+        openFullScreen(){
+            this.$emit('OpenBookImages');
+        },
+        //打开全屏预览
+        showFullScreen() {
+            $('#canvas').hide();
+            this.isShowFullScreen = true;//是否显示全屏
+            this.initBook(); // 初始化翻书效果
+        },
+        // 初始化翻书效果
+        initBook() {
+            const b_height = $(window).height() - 120;//书本高度
+            const b_width = Math.round(b_height * 0.706) * 2;// 书本宽度 0.706 宽高的比例
+            this.$nextTick(() => {
+                $('#canvas').fadeIn(1000);
+                const $book = $(this.$refs.bookContainer);
+                if ($book.width() == 0 || $book.height() == 0) {
+                    setTimeout(loadApp, 10);
+                    return;
+                }
+                // 初始化 turn.js
+                const _that = this;
+                const totalPages = this.bookPages.length;//总页数
+                $book.turn({
+                    // 页面尺寸(A4 高度示例)
+                    // height: 1285, // 单页高度(对应 A4 72dpi 高度)
+                    // width: 1816, // 单页宽度(对应 A4 72dpi 宽度)908
+                    width: b_width, // 书本宽度
+                    height: b_height, // 书本高度
+                    duration: 1000,   //翻页速度,值越小越快
+                    autoCenter: true, // 自动居中
+                    acceleration: false,//设置硬件加速模式,用于触摸设备此值必须是真的
+                    gradients: true,//置翻页时是否显示翻页跟阴影
+                    display: 'double', // 双页显示(适合PC端)
+                    elevation: 50, // 翻页阴影高度(增强立体感)
+                    pages: totalPages,
+                    when: {
+                        // 翻页前触发
+                        turning: function (event, page, view) {
+                            const book = $(this);
+                            const pages = book.turn('pages') - 2;
+                            if(page > 1 && page < pages){
+                                console.log(page , pages)
+                                const prev = view[0] - 1;
+                                const next = view[1] < pages ? view[1] - 1 : '';
+                                _that.currentPage = next?`${prev}-${next}/`:`${prev}/`;
+                            }else{
+                                _that.currentPage = '';
+                            }
+                            // 显示和隐藏按钮
+                            disableControls(page);
+                        },
+                        // 翻页后触发
+                        turned: function (event, page, view) {
+                            // 显示和隐藏按钮
+                            disableControls(page);
+                            $(this).turn('center');
+                            if (page == 1) {
+                                // 表示调用翻页 API 并指定翻页效果为 “Peel( peeling,即 “剥开” 效果)”,且翻页的起始位置为右下角(br 是 bottom right 的缩写)
+                                $(this).turn('peel', 'br');
+                            }
+                        },
+                        // missing: function (event, pages) {
+                        // 	// Add pages that aren't in the magazine
+                        // 	for (var i = 0; i < pages.length; i++){
+                        //         addPage(pages[i], $(this));
+                        //     }
+                        // }
+                    }
+                });
+                // 初始化页码信息
+                // this.totalPages = $book.turn('pages');
+                // Zoom.js
+
+                $('#canvas .magazine-viewport').zoom({
+
+                    flipbook: $('.magazine'),
+
+                    max: function () {
+
+                        return largeMagazineWidth() / $('.magazine').width();
+
+                    },
+
+                    when: {
+
+                        swipeLeft: function () {
+
+                            $(this).zoom('flipbook').turn('next');
+
+                        },
+
+                        swipeRight: function () {
+
+                            $(this).zoom('flipbook').turn('previous');
+
+                        },
+
+
+
+                        resize: function (event, scale, page, pageElement) {
+
+                            // if (scale == 1)
+
+                            //     loadSmallPage(page, pageElement);
+
+                            // else
+
+                            //     loadLargePage(page, pageElement);
+
+                        },
+
+                        zoomIn: function () {
+
+                            $('.made').hide();
+
+                            $('.magazine').removeClass('animated').addClass('zoom-in');
+
+                            $('.zoom-icon').removeClass('zoom-icon-in').addClass('zoom-icon-out');
+
+                            // if (!window.escTip && !$.isTouch) {
+
+                            //     escTip = true;
+
+                            //     $('<div />', { 'class': 'exit-message' }).
+
+                            //         html('<div>Press ESC to exit</div>').
+
+                            //         appendTo($('body')).
+
+                            //         delay(2000).
+
+                            //         animate({ opacity: 0 }, 500, function () {
+
+                            //             $(this).remove();
+
+                            //         });
+
+                            // }
+
+                        },
+
+                        zoomOut: function () {
+
+                            $('.exit-message').hide();
+
+                            $('.thumbnails').fadeIn();
+
+                            $('.made').fadeIn();
+
+                            $('.zoom-icon').removeClass('zoom-icon-out').addClass('zoom-icon-in');
+
+                            setTimeout(function () {
+
+                                $('.magazine').addClass('animated').removeClass('zoom-in');
+
+                                resizeViewport();
+
+                            }, 0);
+
+                        }
+
+                    }
+
+                });
+
+
+                $(document).keydown(function (e) {
+
+                    const previous = 37, next = 39, esc = 27;
+
+                    switch (e.keyCode) {
+
+                        case previous:
+
+                            // left arrow
+
+                            $('.magazine').turn('previous');
+
+                            e.preventDefault();
+
+                            break;
+
+                        case next:
+
+                            //right arrow
+
+                            $('.magazine').turn('next');
+
+                            e.preventDefault();
+
+                            break;
+
+                        case esc:
+
+                            $('#canvas .magazine-viewport').zoom('zoomOut');
+
+                            e.preventDefault();
+
+                            break;
+
+                    }
+
+                });
+
+
+                $(window).resize(function () {
+
+                    resizeViewport();
+
+                }).bind('orientationchange', function () {
+
+                    resizeViewport();
+
+                });
+                // Events for the next button
+
+                $('.next-button').click(function () {
+
+                    $('.magazine').turn('next');
+
+                    setTimeout(function () {
+
+                        setArrows();
+
+                    }, 300);
+
+                });
+
+
+
+                // Events for the next button	
+
+                $('.previous-button').bind($.mouseEvents.over, function () {
+
+                    $(this).addClass('previous-button-hover');
+
+                }).bind($.mouseEvents.out, function () {
+
+                    $(this).removeClass('previous-button-hover');
+
+                }).bind($.mouseEvents.down, function () {
+
+                    $(this).addClass('previous-button-down');
+
+                }).bind($.mouseEvents.up, function () {
+
+                    $(this).removeClass('previous-button-down');
+
+                }).click(function () {
+
+                    $('.magazine').turn('previous');
+
+                    setTimeout(function () {
+
+                        setArrows();
+
+                    }, 300);
+
+                });
+                // Zoom icon
+                $('.zoom_in_icon').on('click', function () {
+                    $('#canvas .magazine-viewport').zoom('zoomIn');
+                });
+                $('.zoom_out_icon').on('click', function () {
+                    $('#canvas .magazine-viewport').zoom('zoomOut');
+                });
+                resizeViewport();
+
+                $('.magazine').addClass('animated');
+            })
+        },
+        //退出全屏
+        CloseFullScreen() {
+            this.isShowFullScreen = false;
+        },
+        // 上一页
+        prevPage() {
+            $('.magazine').turn('previous');
+            setTimeout(function () {
+                setArrows();
+            }, 300);
+        },
+        // 下一页
+        nextPage() {
+            $('.magazine').turn('next');
+            setTimeout(function () {
+                setArrows();
+            }, 300);
+        },
+        async DownloadPdf() {
+            this.loading = true;
+            this.targetProgress = 1;
+            // this.showReportLoading = true;
+            //获取所有的area_page 元素
+            const elements = document.querySelectorAll(".web_mode .web_cover");
+            const pdf = new jsPDF("p", "pt", "a4"); // 'p'表示纵向,'a4'表示A4纸张尺寸
+            const pdfWidth = pdf.internal.pageSize.getWidth(); //获取pdf的宽度
+            const pdfHeight = pdf.internal.pageSize.getHeight(); //获取pdf的高度
+            let yPos = 0; //当前图像在pdf页面上的垂直位置的变量
+            let i = 1;
+            let imageList = [];
+            // 遍历每个 area_page 元素并按顺序处理
+            for (const element of elements) {
+                await html2canvas(element, {
+                    scale: 2, // 增加缩放比例
+                    useCORS: true, // 允许跨域
+                    logging: true, // 是否显示进度条
+                    letterRendering: true, // 文字抗锯齿 启用字母渲染
+                }).then(async(canvas) => {
+                    // 将 Canvas 对象转换为 PNG 格式的 Data URL 字符串
+                    const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
+                    const imgUrl = URL.createObjectURL(blob);
+                    imageList.push(imgUrl)
+                });
+            }
+            imageList.splice(1, 0, ...this.bookPages);
+            // console.log(imageList)
+            const stepLens = imageList?.length || 1;
+            for (const imgData of imageList) {
+                const imgProps = pdf.getImageProperties(imgData); // 获取图像的属性,包括宽度和高度
+                const imgWidth = imgProps.width;
+                const imgHeight = imgProps.height;
+                const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight); //计算图片的缩放比例
+                const adjustWidth = imgWidth * ratio;
+                const adjustHeight = imgHeight * ratio;
+
+                // 在添加每个图像之前,检查当前页面的高度是否足够。如果不够,则添加新页面,并将 yPos 重置为 0
+                if (yPos + adjustHeight > pdfHeight) {
+                    pdf.addPage();
+                    yPos = 0;
+                }
+
+                // 将图像添加到pdf中
+                pdf.addImage(imgData, "PNG", 0, yPos, adjustWidth, adjustHeight);
+                yPos += adjustHeight;
+
+                // 如果添加图像后剩余空间不足一页,则添加新页面
+                if (yPos > pdfHeight) {
+                    pdf.addPage();
+                    yPos = 0;
+                }
+                const currentProgress  = Math.min(Math.round((i / stepLens) * 100),100);
+                this.targetProgress = currentProgress;
+                i++;
+            }
+            // 保存pdf文件
+            pdf.save(`${this.reportTitle}_${this.coverTitle}.pdf`);
+            // this.showReportLoading = false; //关闭加载loading
+            this.loading = false;
+        },
+        // async DownloadPdfNew() {
+        //     //获取所有的area_page 元素
+        //     const elements = document.querySelectorAll(".web_mode .web_area_page");
+        //     this.loading = true;
+        //     // this.targetProgress = 1;
+        //     // this.showReportLoading = true;
+        //     const pdf = new jsPDF("p", "pt", "a4"); // 'p'表示纵向,'a4'表示A4纸张尺寸
+        //     const pdfWidth = pdf.internal.pageSize.getWidth(); //获取pdf的宽度
+        //     const pdfHeight = pdf.internal.pageSize.getHeight(); //获取pdf的高度
+        //     let yPos = 0; //当前图像在pdf页面上的垂直位置的变量
+        //     // let i = 1;
+        //     let imageList = [];
+        //     // 遍历每个 area_page 元素并按顺序处理
+        //     for (const element of elements) {
+        //         await html2canvas(element, {
+        //             scale: 2, // 增加缩放比例
+        //             useCORS: true, // 允许跨域
+        //             logging: true, // 是否显示进度条
+        //             letterRendering: true, // 文字抗锯齿 启用字母渲染
+        //         }).then(async(canvas) => {
+        //             // 将 Canvas 对象转换为 PNG 格式的 Data URL 字符串
+        //             const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
+        //             const imgUrl = URL.createObjectURL(blob);
+        //             imageList.push(imgUrl)
+        //         });
+        //     }
+        //     // const stepLens = imageList?.length || 1;
+        //     for (const imgData of imageList) {
+        //         const imgProps = pdf.getImageProperties(imgData); // 获取图像的属性,包括宽度和高度
+        //         const imgWidth = imgProps.width;
+        //         const imgHeight = imgProps.height;
+        //         const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight); //计算图片的缩放比例
+        //         const adjustWidth = imgWidth * ratio;
+        //         const adjustHeight = imgHeight * ratio;
+
+        //         // 在添加每个图像之前,检查当前页面的高度是否足够。如果不够,则添加新页面,并将 yPos 重置为 0
+        //         if (yPos + adjustHeight > pdfHeight) {
+        //             pdf.addPage();
+        //             yPos = 0;
+        //         }
+
+        //         // 将图像添加到pdf中
+        //         pdf.addImage(imgData, "PNG", 0, yPos, adjustWidth, adjustHeight);
+        //         yPos += adjustHeight;
+
+        //         // 如果添加图像后剩余空间不足一页,则添加新页面
+        //         if (yPos > pdfHeight) {
+        //             pdf.addPage();
+        //             yPos = 0;
+        //         }
+        //         // const currentProgress  = Math.min(Math.round((i / stepLens) * 100),100);
+        //         // this.targetProgress = currentProgress;
+        //         // i++;
+        //     }
+        //     // 保存pdf文件
+        //     pdf.save(`${this.reportTitle}_${this.coverTitle}.pdf`);
+        //     // this.showReportLoading = false; //关闭加载loading
+        //     this.loading = false;
+        //     this.$emit('PdfLoadEnd');
+        // },
+        async DownloadPdfNew() {
+            //获取所有的area_page 元素
+            const elements = document.querySelectorAll(".web_mode .web_area_page");
+            this.loading = true;
+            // this.targetProgress = 1;
+            // this.showReportLoading = true;
+            const pdf = new jsPDF("p", "pt", "a4"); // 'p'表示纵向,'a4'表示A4纸张尺寸
+            const pdfWidth = pdf.internal.pageSize.getWidth(); //获取pdf的宽度
+            const pdfHeight = pdf.internal.pageSize.getHeight(); //获取pdf的高度
+            // 遍历每个 area_page 元素并按顺序处理
+            for (const [pageIndex, element] of Array.from(elements).entries()) {
+                // 确保页面元素存在并可见
+                await this.$nextTick();
+                if (element) {
+                    // 确保元素可见并已渲染
+                    // 确保元素可见并已渲染,并设置为横向排列样式
+                    element.style.visibility = 'visible';
+                    element.offsetHeight; // 触发重排
+                    // 等待元素完全渲染
+                    await new Promise(resolve => setTimeout(resolve, 100));
+                    
+                    // 使用 dom-to-image 生成图片
+                    const dataUrl = await domtoimage.toJpeg(element, {
+                        quality: 0.8,// 降低质量以减小文件大小
+                        // 获取完整尺寸
+                        width: element.scrollWidth*2,
+                        height: element.scrollHeight*2,
+                        // 提高画布分辨率以获得更清晰的图像
+                        // canvasWidth: element.scrollWidth * 2,
+                        // canvasHeight: element.scrollHeight * 2,
+                        // 新增高清配置
+                        // pixelRatio: window.devicePixelRatio * 2,
+                        style: {
+                        'transform': 'scale(2)',
+                        'transform-origin': 'left top',
+                        'background-color': '#ffffff',
+                        // 关键:背景图尺寸适配原元素
+                        'background-size': `${element.scrollWidth}px ${element.scrollHeight}px`, // 优先铺满且完整显示
+                        'background-position': 'top left',
+                        'background-repeat': 'no-repeat', // 防止背景图重复导致视觉不完整
+                        'overflow': 'visible',
+                        'white-space': 'normal',
+                        'position': 'relative',
+                        'visibility': 'visible'
+                        },
+                        bgcolor: '#ffffff'
+                    });
+                    
+                    // 获取图像属性并计算缩放比例
+                    const imgProps = pdf.getImageProperties(dataUrl);
+
+                    const imgWidth = imgProps.width;
+                    const imgHeight = imgProps.height;
+                    
+                    // 计算缩放比例(保持宽高比)
+                    const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight);
+                    const adjustWidth = imgWidth * ratio;
+                    const adjustHeight = imgHeight * ratio;
+
+                    // 居中放置图像
+                    const xPosition = (pdfWidth - adjustWidth) / 2;
+                    const yPosition = (pdfHeight - adjustHeight) / 2;
+                    // console.log("打印pdf图片尺寸", imgWidth, imgHeight);
+                    // console.log("打印pdf页面尺寸", pdfWidth, pdfHeight);
+                    // console.log("打印pdf缩放比例", ratio);
+                    // console.log("打印pdf缩放后尺寸",adjustWidth, adjustHeight);
+                    // console.log("打印pdf图片位置", xPosition, yPosition);
+                    // 如果不是第一页,添加新页面
+                    if (pageIndex > 0) 
+                    {
+                        pdf.addPage();
+                    }
+                    pdf.addImage(dataUrl, "JPEG", xPosition, yPosition - 5, adjustWidth, adjustHeight);
+                }
+            }
+            // 保存pdf文件
+            pdf.save(`${this.reportTitle}_${this.coverTitle}.pdf`);
+            // this.showReportLoading = false; //关闭加载loading
+            this.loading = false;
+            this.$emit('PdfLoadEnd');
+        },
+    }
+};
+</script>
+
+<style scoped lang="scss">
+@import '../components/css/magazine.css'; // 引入 SCSS 文件
+
+.report_page {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 0;
+    box-sizing: border-box;
+    background-color: #F0F4FB;
+}
+
+/* 书本容器样式 */
+// #flipbook {
+//     box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
+// }
+
+/* 单页样式(注意:宽度为书本的一半,因为默认双页显示) */
+// .joint_print_area {
+//     width: 50%;
+//     height: 100%;
+//     background-color: #fff;
+//     overflow: hidden;
+// }
+
+// .area_page {
+//     width: 100%;
+//     height: 100%;
+//     padding: 32px;
+//     box-sizing: border-box;
+//     // background: #FFFFFF;
+//     box-shadow: inset 1px 0px 0px 0px #EEEEEE;
+// }
+
+/* 控制按钮样式 */
+.book_controls {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    width: 100%;
+    height: 60px;
+    background: #FFFFFF;
+    // margin-top: 40px;
+    padding: 12px 24px;
+    box-sizing: border-box;
+
+    .zoom {
+        display: inline-flex;
+        align-items: center;
+
+        .zoom_icon {
+            width: 32px;
+            height: 32px;
+            margin-right: 20px;
+            cursor: pointer;
+        }
+    }
+
+    .page_size {
+        display: inline-flex;
+        align-items: center;
+        .butn_prev{
+            width: 24px;
+            height: 24px;
+            margin-right: 15px;
+            cursor: pointer;
+        }
+        .butn_next{
+            width: 24px;
+            height: 24px;
+            margin-left: 15px;
+            cursor: pointer;
+        }
+        img{
+            width: 24px;
+            height: 24px;
+        }
+        .page_info {
+            font-weight: 500;
+            font-size: 16px;
+            color: #333333;
+        }
+    }
+
+    .download {
+        padding: 8px 16px;
+        height: 36px;
+        background-color: #2E64FA;
+        border-radius: 4px;
+        font-size: 14px;
+        color: #FFFFFF;
+        border: none;
+        cursor: pointer;
+
+        &:disabled {
+            background-color: #ccc;
+            cursor: not-allowed;
+        }
+    }
+}
+</style>

+ 262 - 0
src/views/analysisReport/studentPage/downloadPdf/components/css/magazine.css

@@ -0,0 +1,262 @@
+/* body {
+	overflow: hidden;
+	background-color: #0c0c0c;
+	margin: 0;
+	padding: 0;
+} */
+#canvas {
+	/* position: absolute; */
+	width: 100%;
+	height: 100%;
+}
+.magazine-viewport .container {
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	width: 1816px;
+	height: 1285px;
+	margin: auto;
+}
+.magazine-viewport .magazine {
+	width: 1816px;
+	height: 1285px;
+	left: -661px;
+	top: -300px;
+}
+.magazine-viewport .page {
+	width: 908px;
+	height: 1285px;
+	background-color: white;
+	/* background-repeat: no-repeat;
+	background-size: 100% 100%; */
+}
+.magazine-viewport .zoomer .region {
+	display: none;
+}
+.magazine .region {
+	position: absolute;
+	overflow: hidden;
+	background: #0066FF;
+	opacity: 0.2;
+	-webkit-border-radius: 10px;
+	-moz-border-radius: 10px;
+	-ms-border-radius: 10px;
+	-o-border-radius: 10px;
+	border-radius: 10px;
+	cursor: pointer;
+	-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
+	filter: alpha(opacity=20);
+}
+.magazine .region:hover {
+	opacity: 0.5;
+	-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
+	filter: alpha(opacity=50);
+}
+.magazine .region.zoom {
+	opacity: 0.01;
+	-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=1)";
+	filter: alpha(opacity=1);
+}
+.magazine .region.zoom:hover {
+	opacity: 0.2;
+	-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
+	filter: alpha(opacity=20);
+}
+.magazine .page {
+	-webkit-box-shadow: 0 0 20px rgba(0,0,0,0.2);
+	-moz-box-shadow: 0 0 20px rgba(0,0,0,0.2);
+	-ms-box-shadow: 0 0 20px rgba(0,0,0,0.2);
+	-o-box-shadow: 0 0 20px rgba(0,0,0,0.2);
+	box-shadow: 0 0 20px rgba(0,0,0,0.2);
+}
+.magazine-viewport .page img {
+	-webkit-touch-callout: none;
+	-webkit-user-select: none;
+	-khtml-user-select: none;
+	-moz-user-select: none;
+	-ms-user-select: none;
+	user-select: none;
+	margin: 0;
+}
+.magazine .even .gradient {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	/* padding: 30px; */
+	box-sizing: border-box;
+	background: -webkit-gradient(linear, left top, right top, color-stop(0.95, rgba(255,255,255,0)), color-stop(1, rgba(0,0,0,0.05)));
+	background-image: -webkit-linear-gradient(left, rgba(255,255,255,0) 95%, rgba(0,0,0,0.05) 100%);
+	background-image: -moz-linear-gradient(left, rgba(255,255,255,0) 95%, rgba(0,0,0,0.05) 100%);
+	background-image: -ms-linear-gradient(left, rgba(255,255,255,0) 95%, rgba(0,0,0,0.05) 100%);
+	background-image: -o-linear-gradient(left, rgba(255,255,255,0) 95%, rgba(0,0,0,0.05) 100%);
+	background-image: linear-gradient(left, rgba(255,255,255,0) 95%, rgba(0,0,0,0.05) 100%);
+}
+.magazine .odd .gradient {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	/* padding: 30px; */
+	box-sizing: border-box;
+	background: -webkit-gradient(linear, right top, left top, color-stop(0.95, rgba(255,255,255,0)), color-stop(1, rgba(0,0,0,0.02)));
+	background-image: -webkit-linear-gradient(right, rgba(255,255,255,0) 95%, rgba(0,0,0,0.02) 100%);
+	background-image: -moz-linear-gradient(right, rgba(255,255,255,0) 95%, rgba(0,0,0,0.02) 100%);
+	background-image: -ms-linear-gradient(right, rgba(255,255,255,0) 95%, rgba0,0,0,0.02 100%);
+	background-image: -o-linear-gradient(right, rgba(255,255,255,0) 95%, rgba(0,0,0,0.02) 100%);
+	background-image: linear-gradient(right, rgba(255,255,255,0) 95%, rgba(0,0,0,0.02) 100%);
+}
+.magazine-viewport .zoom-in .even .gradient, .zoom-in .odd .gradient {
+	display: none;
+}
+.magazine-viewport .loader {
+	background-image: url(../image/loader.gif);
+	width: 22px;
+	height: 22px;
+	position: absolute;
+	top: 280px;
+	left: 219px;
+}
+.magazine-viewport .shadow {
+	-webkit-transition: -webkit-box-shadow 0.5s;
+	-moz-transition: -moz-box-shadow 0.5s;
+	-o-transition: -webkit-box-shadow 0.5s;
+	-ms-transition: -ms-box-shadow 0.5s;
+	-webkit-box-shadow: 0 0 20px #ccc;
+	-moz-box-shadow: 0 0 20px #ccc;
+	-o-box-shadow: 0 0 20px #ccc;
+	-ms-box-shadow: 0 0 20px #ccc;
+	box-shadow: 0 0 20px #ccc;
+}
+.next-button {
+ 	width: 59px;
+	height: 59px;
+	position: absolute;
+	top: 50%;
+	/* background:url('../image/arrows.png') -59px 0; */
+	right: 10px;
+	z-index:10;
+	cursor: pointer;
+}
+.next-button .el-icon-arrow-right,.previous-button .el-icon-arrow-left{
+	font-size: 55px;
+	color: #999999;
+}
+.previous-button {
+	width: 59px;
+	height: 59px;
+	position: absolute;
+	top: 50%;
+	/* background-image: url('../image/arrows.png'); */
+	z-index:10;
+	cursor: pointer;
+}
+/* .magazine-viewport .previous-button-hover {
+	background-position: 0 -59px;
+	cursor: pointer;
+}
+.magazine-viewport .next-button-hover {
+	background-position: -59px -59px;
+	cursor: pointer;
+} */
+.magazine-viewport .zoom-in .next-button, .magazine-viewport .zoom-in .previous-button {
+	display: none;
+}
+.animated {
+	-webkit-transition: margin-left 0.5s;
+	-moz-transition: margin-left 0.5s;
+	-ms-transition: margin-left 0.5s;
+	-o-transition: margin-left 0.5s;
+	transition: margin-left 0.5s;
+}
+.thumbnails{
+	position:absolute;
+	bottom:0;
+	left:0;
+	width:100%;
+	height:140px;
+	z-index:1;
+	display: flex;
+	align-items: flex-end;
+}
+.exit-message {
+	position: absolute;
+	top: 10px;
+	left: 0;
+	width: 100%;
+	height: 40px;
+	z-index: 10000;
+}
+.exit-message > div {
+	width: 140px;
+	height: 30px;
+	margin: auto;
+	background: rgba(0,0,0,0.5);
+	text-align: center;
+	font: 12px arial;
+	line-height: 30px;
+	color: white;
+	-webkit-border-radius: 10px;
+	-moz-border-radius: 10px;
+	-ms-border-radius: 10px;
+	-o-border-radius: 10px;
+	border-radius: 10px;
+}
+/* .zoom-icon {
+	position: absolute;
+	z-index: 1000;
+	width: 22px;
+	height: 22px;
+	top: 10px;
+	right: 10px;
+	background-image: url(../image/zoom-icons.png);
+	background-size: 88px 22px;
+}
+.zoom-icon-in {
+	background-position: 0 0;
+	cursor: pointer;
+}
+.zoom-icon-in.zoom-icon-in-hover {
+	background-position: -22px 0;
+	cursor: pointer;
+}
+.zoom-icon-out {
+	background-position: -44px 0;
+}
+.zoom-icon-out.zoom-icon-out-hover {
+	background-position: -66px 0;
+	cursor: pointer;
+} */
+.close-zoom-icon{
+	width: 100px;
+	height: 36px;
+	background: #FFFFFF;
+	border-radius: 4px;
+	border: 1px solid #DCDFE6;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	cursor: pointer;
+	position: absolute;
+	top: 40px;
+	right: 24px;
+	z-index: 10;
+}
+.close-zoom-icon .close{
+	width: 20px;
+	height: 20px;
+	margin-right: 6px;
+}
+.close-zoom-icon span{
+	font-weight: 400;
+	font-size: 14px;
+	color: #666666;
+}
+/* .bottom {
+	position: absolute;
+	left: 0;
+	bottom: 0;
+	width: 100%;
+} */

BIN
src/views/analysisReport/studentPage/downloadPdf/components/image/loader.gif


+ 325 - 0
src/views/analysisReport/studentPage/downloadPdf/components/js/magazine.js

@@ -0,0 +1,325 @@
+/*
+ * Magazine sample
+ */
+
+export function setArrows() {
+    /*var width = $(window).width();
+  
+      //alert("chushihua"+width);		
+      var height = $(window).height();
+      var bookWidth = $(".magazine").width();
+      var bookHeight = $(".magazine").height();
+      //alert("chushihua"+bookWidth);		
+      var arrowSize = $(".next-button").width();
+      //alert(arrowSize);
+     alert($(".magazine").offset().left+"\n"+$('.next-button').offset().left);
+  
+      var LeftArrowLeft = - ( width - bookWidth ) / 4 + 'px' ;
+      //alert(LeftArrowLeft);
+      var RightArrowLeft = - ( width - bookWidth+ arrowSize*2) / 4 + 'px' ;
+      //alert(RightArrowLeft); 
+      //alert(RightArrowLeft);
+          //alert("zhihou"+bookWidth);	
+      	
+      $('.next-button').css( "right",RightArrowLeft );
+      $('.previous-button').css( "left", LeftArrowLeft );*/
+    setTimeout(function () {
+        var width = $(window).width();
+        var bookWidth = $(".magazine").width();
+        var arrowSize = $(".next-button").width();
+        var magaLeft = $(".magazine").offset().left;
+        var nextLeft = (width - bookWidth - magaLeft - 60) / 2;
+        //alert("width "+width +"\nbookWidth :"+bookWidth +"\nmagaLeft:"+magaLeft+"\nnextLeft:"+nextLeft);
+        $(".next-button").animate({ right: nextLeft }, 300);
+
+        $(".previous-button").animate({ left: nextLeft }, 300);
+    }, 100);
+}
+
+export  function addPage(page, book) {
+    var id,pages = book.turn("pages");
+
+    // Create a new element for this page
+    var element = $("<div />", {});
+
+    // Add the page to the flipbook
+    if (book.turn("addPage", element, page)) {
+        // Add the initial HTML
+        // It will contain a loader indicator and a gradient
+        element.html('<div class="gradient"></div><div class="loader"></div>');
+
+        // Load the page
+        console.log(page, element,99999)
+        loadPage(page, element);
+    }
+}
+
+function loadPage(page, pageElement) {
+    // Create an image element
+    var img = $("<img />");
+
+    img.mousedown(function (e) {
+        e.preventDefault();
+    });
+
+    img.load(function () {
+        // Set the size
+        $(this).css({
+            width: "100%",
+            height: "100%",
+        });
+
+        // Add the image to the page after loaded
+        $(this).appendTo(pageElement);
+
+        // Remove the loader indicator
+        pageElement.find(".loader").remove();
+    });
+
+    // Load the page
+    img.attr("src", "pages/" + page + ".png");
+
+    loadRegions(page, pageElement);
+}
+
+// Zoom in / Zoom out
+export function zoomTo(event) {
+    setTimeout(function () {
+        if ($("#canvas .magazine-viewport").data().regionClicked) {
+            $("#canvas .magazine-viewport").data().regionClicked = false;
+        } else {
+            if ($("#canvas .magazine-viewport").zoom("value") == 1) {
+                $("#canvas .magazine-viewport").zoom("zoomIn", event);
+            } else {
+                $("#canvas .magazine-viewport").zoom("zoomOut");
+            }
+        }
+    }, 1);
+}
+
+// Load regions
+export function loadRegions(page, element) {
+    $.getJSON("pages/" + page + "-regions.json").done(function (data) {
+        $.each(data, function (key, region) {
+            addRegion(region, element);
+        });
+    });
+}
+
+// Add region
+export function addRegion(region, pageElement) {
+    var reg = $("<div />", {
+        class: "region  " + region["class"],
+    }),
+        options = $(".magazine").turn("options"),
+        pageWidth = options.width / 2,
+        pageHeight = options.height;
+
+    reg.css({
+        top: Math.round((region.y / pageHeight) * 100) + "%",
+        left: Math.round((region.x / pageWidth) * 100) + "%",
+        width: Math.round((region.width / pageWidth) * 100) + "%",
+        height: Math.round((region.height / pageHeight) * 100) + "%",
+    }).attr("region-data", $.param(region.data || ""));
+
+    reg.appendTo(pageElement);
+}
+
+// Process click on a region
+export function regionClick(event) {
+    var region = $(event.target);
+
+    if (region.hasClass("region")) {
+        $("#canvas .magazine-viewport").data().regionClicked = true;
+
+        setTimeout(function () {
+            $("#canvas .magazine-viewport").data().regionClicked = false;
+        }, 100);
+
+        var regionType = $.trim(region.attr("class").replace("region", ""));
+
+        return processRegion(region, regionType);
+    }
+}
+
+// Process the data of every region
+export function processRegion(region, regionType) {
+    data = decodeParams(region.attr("region-data"));
+
+    switch (regionType) {
+        case "link":
+            window.open(data.url);
+
+            break;
+        case "zoom":
+            var regionOffset = region.offset(),
+                viewportOffset = $("#canvas .magazine-viewport").offset(),
+                pos = {
+                    x: regionOffset.left - viewportOffset.left,
+                    y: regionOffset.top - viewportOffset.top,
+                };
+
+            $("#canvas .magazine-viewport").zoom("zoomIn", pos);
+
+            break;
+        case "to-page":
+            $(".magazine").turn("page", data.page);
+            break;
+    }
+}
+
+// Load large page
+export function loadLargePage(page, pageElement) {
+    var img = $("<img />");
+
+    img.load(function () {
+        var prevImg = pageElement.find("img");
+        $(this).css({
+            width: "100%",
+            height: "100%",
+        });
+        $(this).appendTo(pageElement);
+        prevImg.remove();
+    });
+
+    // Loadnew page
+    img.attr("src", "pages/" + page + "-large.png");
+}
+
+// Load small page
+export function loadSmallPage(page, pageElement) {
+    var img = pageElement.find("img");
+
+    img.css({
+        width: "100%",
+        height: "100%",
+    });
+
+    img.unbind("load");
+    // Loadnew page
+    img.attr("src", "pages/" + page + ".png");
+}
+
+// http://code.google.com/p/chromium/issues/detail?id=128488
+export function isChrome() {
+    return navigator.userAgent.indexOf("Chrome") != -1;
+}
+
+export function disableControls(page) {
+    if (page == 1) $(".previous-button").hide();
+    else $(".previous-button").show();
+
+    if (page == $(".magazine").turn("pages")) $(".next-button").hide();
+    else $(".next-button").show();
+}
+
+// Set the width and height for the viewport
+export function resizeViewport() {
+    var width = $(window).width(),
+        height = $(window).height(),
+        options = $(".magazine").turn("options");
+
+    $(".magazine").removeClass("animated");
+
+    $("#canvas .magazine-viewport")
+        .css({
+            width: width,
+            height: height,
+        })
+        .zoom("resize");
+    setArrows();
+
+    if ($(".magazine").turn("zoom") == 1) {
+        var bound = calculateBound({
+            width: options.width,
+            height: options.height,
+            boundWidth: Math.min(options.width, width),
+            boundHeight: Math.min(options.height, height),
+        });
+
+        if (bound.width % 2 !== 0) bound.width -= 1;
+
+        if (
+            bound.width != $(".magazine").width() ||
+            bound.height != $(".magazine").height()
+        ) {
+            $(".magazine").turn("size", bound.width, bound.height);
+
+            if ($(".magazine").turn("page") == 1) $(".magazine").turn("peel", "br");
+        }
+
+        $(".magazine").css({
+            top: -bound.height / 2 - 30,
+            left: -bound.width / 2,
+        });
+    }
+    var magazineOffset = $(".magazine").offset(),
+        boundH = height - magazineOffset.top - $(".magazine").height(),
+        marginTop = (boundH - $(".thumbnails > div").height()) / 2;
+    if (marginTop < 0) {
+        $(".thumbnails").css({
+            height: 1,
+        });
+    } else {
+        $(".thumbnails").css({
+            height: boundH,
+        });
+        // $(".thumbnails > div").css({
+        //     marginTop: marginTop,
+        // });
+    }
+
+    if (magazineOffset.top < $(".made").height()) $(".made").hide();
+    else $(".made").show();
+
+    $(".magazine").addClass("animated");
+}
+
+// Number of views in a flipbook
+export function numberOfViews(book) {
+    return book.turn("pages") / 2 + 1;
+}
+
+// Current view in a flipbook
+export function getViewNumber(book, page) {
+    return parseInt((page || book.turn("page")) / 2 + 1, 10);
+}
+
+// Width of the flipbook when zoomed in
+export function largeMagazineWidth() {
+    return 2214;
+}
+
+// decode URL Parameters
+export function decodeParams(data) {
+    var parts = data.split("&"),
+        d,
+        obj = {};
+    for (var i = 0; i < parts.length; i++) {
+        d = parts[i].split("=");
+        obj[decodeURIComponent(d[0])] = decodeURIComponent(d[1]);
+    }
+    return obj;
+}
+
+// Calculate the width and height of a square within another square
+export function calculateBound(d) {
+    var bound = {
+        width: d.width,
+        height: d.height,
+    };
+    if (bound.width > d.boundWidth || bound.height > d.boundHeight) {
+        var rel = bound.width / bound.height;
+        if (
+            d.boundWidth / rel > d.boundHeight &&
+            d.boundHeight * rel <= d.boundWidth
+        ) {
+            bound.width = Math.round(d.boundHeight * rel);
+            bound.height = d.boundHeight;
+        } else {
+            bound.width = d.boundWidth;
+            bound.height = Math.round(d.boundWidth / rel);
+        }
+    }
+    return bound;
+}

+ 26 - 0
src/views/analysisReport/studentPage/downloadPdf/components/js/zoom.min.js

@@ -0,0 +1,26 @@
+/* turn.js 4.1.0 | Copyright (c) 2012 Emmanuel Garcia | turnjs.com | turnjs.com/license.txt */
+
+(function(f){function r(a,b){return a[0]==b[0]?!1:a.attr("page")?!0:a.parent()[0]?r(a.parent(),b):!1}function u(a){function b(a){this.name="TurnJsError";this.message=a}b.prototype=Error();b.prototype.constructor=b;return new b(a)}function t(a,b,d){return q&&d?" translate3d("+a+"px,"+b+"px, 0px) ":" translate("+a+"px, "+b+"px) "}function v(a,b){return q&&b?" scale3d("+a+", "+a+", 1) ":" scale("+a+") "}function h(a,b){return{x:a,y:b}}function m(a,b){return function(){return a.apply(b,arguments)}}var q,
+w={max:2,flipbook:null,easeFunction:"ease-in-out",duration:500,when:{}},j={init:function(a){var b=this,d=this.data(),a=f.extend({},w,a);if(!a.flipbook||!a.flipbook.turn("is"))throw u("options.flipbook is required");q="WebKitCSSMatrix"in window||"MozPerspective"in document.body.style;if("function"!=typeof a.max){var e=a.max;a.max=function(){return e}}d.zoom={opts:a,axis:h(0,0),scrollPos:h(0,0),eventQueue:[],mouseupEvent:function(){return j._eMouseUp.apply(b,arguments)},eventTouchStart:m(j._eTouchStart,
+b),eventTouchMove:m(j._eTouchMove,b),eventTouchEnd:m(j._eTouchEnd,b),flipbookEvents:{zooming:m(j._eZoom,b),pressed:m(j._ePressed,b),released:m(j._eReleased,b),start:m(j._eStart,b),turning:m(j._eTurning,b),turned:m(j._eTurned,b),destroying:m(j._eDestroying,b)}};for(var c in a.when)Object.prototype.hasOwnProperty.call(a.when,c)&&this.bind("zoom."+c,a.when[c]);for(c in d.zoom.flipbookEvents)Object.prototype.hasOwnProperty.call(d.zoom.flipbookEvents,c)&&a.flipbook.bind(c,d.zoom.flipbookEvents[c]);this.css({position:"relative",
+overflow:"hidden"});f.isTouch?(a.flipbook.bind("touchstart",d.zoom.eventTouchStart).bind("touchmove",d.zoom.eventTouchMove).bind("touchend",d.zoom.eventTouchEnd),this.bind("touchstart",j._tap)):this.mousedown(j._mousedown).click(j._tap)},_tap:function(a){var b=f(this),d=b.data().zoom;!d.draggingCorner&&!d.dragging&&r(f(a.target),b)&&(j._addEvent.call(b,"tap",a),(a=j._eventSeq.call(b))&&b.trigger(a))},_addEvent:function(a,b){var d=this.data().zoom,e=(new Date).getTime();d.eventQueue.push({name:a,timestamp:e,
+event:b});10<d.eventQueue.length&&d.eventQueue.splice(0,1)},_eventSeq:function(){var a=this.data().zoom.eventQueue,b=a.length-1;if(0<b&&"tap"==a[b].name&&"tap"==a[b-1].name&&a[b].event.pageX==a[b-1].event.pageX&&a[b].event.pageY==a[b-1].event.pageY&&200>a[b].timestamp-a[b-1].timestamp&&50<a[b].timestamp-a[b-1].timestamp)return f.extend(a[b].event,{type:"zoom.doubleTap"});if("tap"==a[b].name)return f.extend(a[b].event,{type:"zoom.tap"})},_prepareZoom:function(){var a,b=0,d=this.data().zoom,e=1/this.zoom("value");
+a=d.opts.flipbook;var c=a.turn("direction"),j=a.data(),l=a.offset(),i=this.offset(),g={height:a.height()},k=a.turn("view");"double"==a.turn("display")&&a.data().opts.autoCenter?k[0]?k[1]?(g.width=a.width(),a=h(l.left-i.left,l.top-i.top)):(g.width=a.width()/2,b="ltr"==c?0:g.width,a=h("ltr"==c?l.left-i.left:l.left-i.left+g.width,l.top-i.top)):(g.width=a.width()/2,b="ltr"==c?g.width:0,a=h("ltr"==c?l.left-i.left+g.width:l.left-i.left,l.top-i.top)):(g.width=a.width(),a=h(l.left-i.left,l.top-i.top));d.zoomer||
+(d.zoomer=f("<div />",{"class":"zoomer",css:{overflow:"hidden",position:"absolute",zIndex:"1000000"}}).mousedown(function(){return!1}).appendTo(this));d.zoomer.css({top:a.y,left:a.x,width:g.width,height:g.height});c=k.join(",");if(c!=d.zoomerView){d.zoomerView=c;d.zoomer.find("*").remove();for(c=0;c<k.length;c++)if(k[c]){var i=j.pageObjs[k[c]].offset(),m=f(j.pageObjs[k[c]]);m.clone().transform("").css({width:m.width()*e,height:m.height()*e,position:"absolute",display:"",top:(i.top-l.top)*e,left:(i.left-
+l.left-b)*e}).appendTo(d.zoomer)}}return{pos:a,size:g}},value:function(){return this.data().zoom.opts.flipbook.turn("zoom")},zoomIn:function(a){var b=this,d=this.data().zoom,e=d.opts.flipbook,c=d.opts.max();e.offset();var s=this.offset();if(d.zoomIn)return this;e.turn("stop");var l=f.Event("zoom.change");this.trigger(l,[c]);if(l.isDefaultPrevented())return this;var i=j._prepareZoom.call(this),g=i.pos,k=h(i.size.width/2,i.size.height/2),l=f.cssPrefix(),m=f.cssTransitionEnd(),q=e.data().opts.autoCenter;
+d.scale=c;e.data().noCenter=!0;a="undefined"!=typeof a?"x"in a&&"y"in a?h(a.x-g.x,a.y-g.y):f.isTouch?h(a.originalEvent.touches[0].pageX-g.x-s.left,a.originalEvent.touches[0].pageY-g.y-s.top):h(a.pageX-g.x-s.left,a.pageY-g.y-s.top):h(k.x,k.y);if(0>a.x||0>a.y||a.x>i.width||a.y>i.height)a.x=k.x,a.y=k.y;var n=h((a.x-k.x)*c+k.x,(a.y-k.y)*c+k.y),a=h(i.size.width*c>this.width()?a.x-n.x:0,i.size.height*c>this.height()?a.y-n.y:0),n=h(Math.abs(i.size.width*c-this.width()),Math.abs(i.size.height*c-this.height())),
+i=h(Math.min(0,i.size.width*c-this.width()),Math.min(0,i.size.height*c-this.height())),p=h(k.x*c-k.x-g.x-a.x,k.y*c-k.y-g.y-a.y);p.y>n.y?a.y=p.y-n.y+a.y:p.y<i.y&&(a.y=p.y-i.y+a.y);p.x>n.x?a.x=p.x-n.x+a.x:p.x<i.x&&(a.x=p.x-i.x+a.x);p=h(k.x*c-k.x-g.x-a.x,k.y*c-k.y-g.y-a.y);g={};g[l+"transition"]=l+"transform "+d.opts.easeFunction+" "+d.opts.duration+"ms";var r=function(){b.trigger("zoom.zoomIn");d.zoomIn=!0;d.flipPosition=h(e.css("left"),e.css("top"));e.turn("zoom",c).css({position:"absolute",margin:"",
+top:0,left:0});var a=e.offset();d.axis=h(a.left-s.left,a.top-s.top);if(q&&"double"==e.turn("display")&&("ltr"==e.turn("direction")&&!e.turn("view")[0]||"rtl"==e.turn("direction")&&!e.turn("view")[1]))d.axis.x+=e.width()/2;b.zoom("scroll",p);b.bind(f.mouseEvents.down,j._eMouseDown);b.bind(f.mouseEvents.move,j._eMouseMove);f(document).bind(f.mouseEvents.up,d.mouseupEvent);b.bind("mousewheel",j._eMouseWheel);setTimeout(function(){d.zoomer.hide();d.zoomer.remove();d.zoomer=null;d.zoomerView=null},50)};
+d.zoomer.css(g).show();m?d.zoomer.bind(m,function(){f(this).unbind(m);r()}):setTimeout(r,d.opts.duration);d.zoomer.transform(t(a.x,a.y,!0)+v(c,!0));return this},zoomOut:function(a){var b,d=this,e=this.data().zoom,c=e.opts.flipbook,m=1/e.scale,l=f.cssPrefix(),i=f.cssTransitionEnd();b=this.offset();a="undefined"!=typeof a?a:e.opts.duration;if(e.zoomIn){var g=f.Event("zoom.change");this.trigger(g,[1]);if(g.isDefaultPrevented())return this;e.zoomIn=!1;e.scale=1;c.data().noCenter=!1;d.unbind(f.mouseEvents.down,
+j._eMouseDown);d.unbind(f.mouseEvents.move,j._eMouseMove);f(document).unbind(f.mouseEvents.up,e.mouseupEvent);d.unbind("mousewheel",j._eMouseWheel);g={};g[l+"transition"]=l+"transform "+e.opts.easeFunction+" "+a+"ms";c.css(g);var k=f("<div />",{css:{position:"relative",top:e.flipPosition.y,left:e.flipPosition.x,width:c.width()*m,height:c.height()*m,background:"blue"}}).appendTo(c.parent()),g=h(k.offset().left-b.left,k.offset().top-b.top);k.remove();var q=c.data().opts.autoCenter;q&&"double"==c.turn("display")&&
+(c.turn("view")[0]?c.turn("view")[1]||(g.x="ltr"==c.turn("direction")?g.x+k.width()/4:g.x-k.width()/4):g.x="ltr"==c.turn("direction")?g.x-k.width()/4:g.x+k.width()/4);var r=f.findPos(c[0]);b=h(-c.width()/2-r.left+k.width()/2+g.x+b.left,-c.height()/2-r.top+k.height()/2+g.y+b.top);var n=function(){c[0].style.removeProperty?(c[0].style.removeProperty(l+"transition"),c.transform(c.turn("options").acceleration?t(0,0,!0):"").turn("zoom",1),c[0].style.removeProperty("margin"),c.css({position:"relative",
+top:e.flipPosition.y,left:e.flipPosition.x})):c.transform("none").turn("zoom",1).css({margin:"",top:e.flipPosition.y,left:e.flipPosition.x,position:"relative"});q&&c.turn("center");d.trigger("zoom.zoomOut")};0===a?n():(i?c.bind(i,function(){f(this).unbind(i);n()}):setTimeout(n,a),c.transform(t(b.x,b.y,!0)+v(m,!0)));return this}},flipbookWidth:function(){var a=this.data().zoom.opts.flipbook,b=a.turn("view");return"double"==a.turn("display")&&(!b[0]||!b[1])?a.width()/2:a.width()},scroll:function(a,
+b,d){var e=this.data().zoom,c=e.opts.flipbook,j=this.zoom("flipbookWidth"),l=f.cssPrefix();if(q){var i={};i[l+"transition"]=d?l+"transform 200ms":"none";c.css(i);c.transform(t(-e.axis.x-a.x,-e.axis.y-a.y,!0))}else c.css({top:-e.axis.y-a.y,left:-e.axis.x-a.x});if(!b){var g,b=h(Math.min(0,(j-this.width())/2),Math.min(0,(c.height()-this.height())/2)),c=h(j>this.width()?j-this.width():(j-this.width())/2,c.height()>this.height()?c.height()-this.height():(c.height()-this.height())/2);a.y<b.y?(a.y=b.y,g=
+!0):a.y>c.y&&(a.y=c.y,g=!0);a.x<b.x?(a.x=b.x,g=!0):a.x>c.x&&(a.x=c.x,g=!0);g&&this.zoom("scroll",a,!0,!0)}e.scrollPos=h(a.x,a.y)},resize:function(){var a=this.data().zoom,b=a.opts.flipbook;if(1<this.zoom("value")){var d=b.offset(),e=this.offset();a.axis=h(d.left-e.left+(a.axis.x+a.scrollPos.x),d.top-e.top+(a.axis.y+a.scrollPos.y));"double"==b.turn("display")&&("ltr"==b.turn("direction")&&!b.turn("view")[0])&&(a.axis.x+=b.width()/2);this.zoom("scroll",a.scrollPos)}},_eZoom:function(){for(var a=this.data().zoom,
+b=a.opts.flipbook,d=b.turn("view"),e=0;e<d.length;e++)d[e]&&this.trigger("zoom.resize",[a.scale,d[e],b.data().pageObjs[d[e]]])},_eStart:function(a){1!=this.zoom("value")&&a.preventDefault()},_eTurning:function(a,b,d){var e=this,a=this.zoom("value"),c=this.data().zoom,b=c.opts.flipbook;c.page=b.turn("page");if(1!=a){for(c=0;c<d.length;c++)d[c]&&this.trigger("zoom.resize",[a,d[c],b.data().pageObjs[d[c]]]);setTimeout(function(){e.zoom("resize")},0)}},_eTurned:function(a,b){if(1!=this.zoom("value")){var d=
+this.data().zoom,e=d.opts.flipbook;b>d.page?this.zoom("scroll",h(0,d.scrollPos.y),!1,!0):b<d.page&&this.zoom("scroll",h(e.width(),d.scrollPos.y),!1,!0)}},_ePressed:function(){f(this).data().zoom.draggingCorner=!0},_eReleased:function(){var a=f(this).data().zoom;setTimeout(function(){a.draggingCorner=!1},1)},_eMouseDown:function(a){f(this).data().zoom.draggingCur=f.isTouch?h(a.originalEvent.touches[0].pageX,a.originalEvent.touches[0].pageY):h(a.pageX,a.pageY);return!1},_eMouseMove:function(a){var b=
+f(this).data().zoom;if(b.draggingCur){b.dragging=!0;var a=f.isTouch?h(a.originalEvent.touches[0].pageX,a.originalEvent.touches[0].pageY):h(a.pageX,a.pageY),d=h(a.x-b.draggingCur.x,a.y-b.draggingCur.y);f(this).zoom("scroll",h(b.scrollPos.x-d.x,b.scrollPos.y-d.y),!0);b.draggingCur=a;return!1}},_eMouseUp:function(){var a=f(this).data().zoom;a.dragging&&f(this).zoom("scroll",a.scrollPos);a.draggingCur=null;setTimeout(function(){a.dragging=!1},1)},_eMouseWheel:function(a,b,d,e){a=f(this).data().zoom;d=
+h(a.scrollPos.x+10*d,a.scrollPos.y-10*e);f(this).zoom("scroll",d,!1,!0)},_eTouchStart:function(a){var b=f(this).data().zoom,a=h(a.originalEvent.touches[0].pageX,a.originalEvent.touches[0].pageY);b.touch={};b.touch.initial=a;b.touch.last=a;b.touch.timestamp=(new Date).getTime();b.touch.speed=h(0,0)},_eTouchMove:function(a){var b=f(this).data().zoom,d=f(this).zoom("value"),e=b.opts.flipbook,c=(new Date).getTime(),a=h(a.originalEvent.touches[0].pageX,a.originalEvent.touches[0].pageY);b.touch&&(1==d&&
+!e.data().mouseAction)&&(b.touch.motion=h(a.x-b.touch.last.x,a.y-b.touch.last.y),b.touch.speed.x=0===b.touch.speed.x?b.touch.motion.x/(c-b.touch.timestamp):(b.touch.speed.x+b.touch.motion.x/(c-b.touch.timestamp))/2,b.touch.last=a,b.touch.timestamp=c)},_eTouchEnd:function(){var a=f(this).data().zoom;if(a.touch&&1==f(this).zoom("value")){var b=Math.abs(a.touch.initial.y-a.touch.last.y);50>b&&(-1>a.touch.speed.x||-100>a.touch.last.x-a.touch.initial.x)?this.trigger("zoom.swipeLeft"):50>b&&(1<a.touch.speed.x||
+100<a.touch.last.x-a.touch.initial.x)&&this.trigger("zoom.swipeRight")}},_eDestroying:function(){var a=this,b=this.data().zoom,d=b.opts.flipbook;this.zoom("zoomOut",0);f.each("tap doubleTap resize zoomIn zoomOut swipeLeft swipeRight".split(" "),function(b,d){a.unbind("zoom."+d)});for(var e in b.flipbookEvents)Object.prototype.hasOwnProperty.call(b.flipbookEvents,e)&&d.unbind(e,b.flipbookEvents[e]);d.unbind("touchstart",b.eventTouchStart).unbind("touchmove",b.eventTouchMove).unbind("touchend",b.eventTouchEnd);
+this.unbind("touchstart",j._tap).unbind("click",j._tap);b=null;this.data().zoom=null}};f.extend(f.fn,{zoom:function(){var a=arguments;if(!a[0]||"object"==typeof a[0])return j.init.apply(f(this[0]),a);if(j[a[0]])return j[a[0]].apply(f(this[0]),Array.prototype.slice.call(a,1));throw u(a[0]+" is not a method");}})})(jQuery);

+ 1432 - 0
src/views/analysisReport/studentPage/downloadPdf/studentReport.vue

@@ -0,0 +1,1432 @@
+<template>
+    <!-- 学生报告 -->
+    <!-- v-loading="loading" element-loading-spinner="el-icon-loading" element-loading-background="rgba(255, 255, 255, 0.5)" :element-loading-text="loadingText" -->
+    <BookFlip :book-pages="bookPageImages" bookBg="student" :requestLoading="loading" :openLoading="openLoading" :coverTitle="`${stuClasName}${studentName}分析报告册`" subtitle="供参考学生使用" ref="bookFlipBox" @OpenBookImages="OpenBookImages" @PdfLoadEnd="PdfLoadEnd">
+        <!-- 自定义每页内容 -->
+        <template #default="slotProps">
+            <template v-if="slotProps.type == 'web_mode'">
+                <div class="area_page" style="position: absolute;top: -9999999px;z-index: -10;">
+                    <!-- 用于获取div的高度 默认隐藏不显示 -->
+                    <div class="area_title" ref="areaReportTitle">
+                        <p>{{ reportTitle }}</p>
+                        <p>{{ stuClasName }}{{ studentName }}分析报告</p>
+                    </div>
+                    <div class="area_module" ref="standardScoreChartDes">
+                        <div class="area_module_describe" v-if="multiSubjectData.maxSubject || multiSubjectData.minSubject" style="margin-top: 0;">
+                            说明:从标准分情况来看,这次考试<template v-if="multiSubjectData.maxSubject"><span style="color: #3ba272">{{ multiSubjectData.maxSubject }}</span>表现突出,请继续保持</template><template v-if="multiSubjectData.minSubject">;<span style="color: #ee6666">{{ multiSubjectData.minSubject }}</span>标准分明显低于其他学科,可能会对总体排名造成影响,可结合错题梳理核心知识点,精准定位薄弱环节,制定针对性的提升计划,以实现各科均衡发展,进一步巩固整体成绩</template>。
+                        </div>
+                    </div>
+                    <div class="area_module" ref="multiSuggestionModule">
+                        <div class="area_module_title">总结建议</div>
+                        <div class="area_module_describe" style="margin-top: 0;" v-html="suggestionHtml"></div>
+                        <div class="pring_jg"></div>
+                    </div>
+                    <template v-for="(subject,subKey) in multiSubjectData.singleSubject">
+                        <div class="area_module" :ref="`singleSubjectSuggestion_${subKey}`" v-if="singleSubjectData?.[subKey]?.suggestionHtml">
+                            <div class="area_module_title">总结建议</div>
+                            <div class="area_module_describe" style="margin-top: 0;" v-html="singleSubjectData?.[subKey]?.suggestionHtml"></div>
+                            <div class="pring_jg"></div>
+                        </div>
+                    </template>
+                </div>
+                <div class="area_page web_area_page" v-for="page in pageCount" :key="`${pageKey}_${page}`">
+                    <template v-if="page == 1">
+                        <div class="area_title">
+                            <p>{{ reportTitle }}</p>
+                            <p>{{ stuClasName }}{{ studentName }}分析报告</p>
+                        </div>
+                        <div class="area_header bg_purple">
+                            <img :src="headerLeftIcon" class="header_icon_left" />
+                            总分成绩分析
+                            <img :src="headerRightIcon" class="header_icon_right" />
+                        </div>
+                        <div class="pring_jg"></div>
+                    </template>
+                    <template v-for="(tableData,index) in multiSubjectData.tableList">
+                        <template v-for="(itemTable,itemIndex) in tableData">
+                            <div class="area_module" v-if="multiSubjectData.tablePagesNum[index][itemIndex] == page && itemTable.length > 0">
+                                <div class="area_module_title">成绩单</div>
+                                <div class="area_module_table">
+                                    <el-table border :data="itemTable" stripe align="center">
+                                        <el-table-column v-for="header in (multiSubjectData?.staticHeaderData ?? [])" align="center" :label="header.name" min-width="100" show-overflow-tooltip>
+                                            <template slot-scope="scope">
+                                                {{ scope.row?.[header.prop] || '-' }}
+                                            </template>
+                                        </el-table-column> 
+                                        <el-table-column v-for="header in (multiSubjectData?.headerList?.[index]?.[itemIndex] ?? [])" align="center" :label="header.name" min-width="100" show-overflow-tooltip>
+                                            <template slot-scope="scope">
+                                                <template v-if="header.prop == 'score'">
+                                                    <!-- * 1-得分显示分数,小题分显示分数,2-得分显示分数,小题分显示对错
+                                                    * 3-得分显示对错,小题分显示分数,4-得分显示对错,小题分显示对错
+                                                    * 5-得分显示等级,小题分显示分数,6-得分显示等级,小题分显示对错 -->
+                                                    <template v-if="multiSubjectData.studentOpenness == 3 || multiSubjectData.studentOpenness == 4">
+                                                    <img class="right_or_wrong_icon" v-if="scope.row.fullScore == scope.row?.[header.prop]" src="@/assets/report/score_yes_icon.webp" />
+                                                    <img class="right_or_wrong_icon" v-else-if="scope.row?.[header.prop] == 0" src="@/assets/report/score_no_icon.webp" />
+                                                    <img class="right_or_wrong_icon" v-else src="@/assets/report/score_dimidiate_icon.webp" />
+                                                    </template>
+                                                    <template v-else>{{ scope.row?.[header.prop] || '-' }}</template>
+                                                </template>
+                                                <template v-else>{{ scope.row?.[header.prop] || '-' }}</template>
+                                                </template>
+                                        </el-table-column>
+                                    </el-table>
+                                </div>
+                            </div>
+                            <template v-if="multiSubjectData.tablePagesNum[index][itemIndex] == page && itemTable.length > 0">
+                                <div class="pring_jg" style="height: 29px;border-bottom: 1px solid #F3F3F3;box-sizing: border-box;"></div>
+                                <div class="pring_jg"></div>
+                            </template>
+                        </template>
+                    </template>
+                    <div class="area_module" v-if="multiSubjectData.chartPagesNum==page && ((multiSubjectData.standardScoreAnalysisStatus === 0 && schoolType == 2) || schoolType == 1) && multiSubjectData.datay.length>0">
+                        <div class="area_module_title">标准分分析图</div>
+                        <div class="area_module_chart">
+                            <DifferenceChart v-if="multiSubjectData.datay.length" :datax="multiSubjectData.datax" unit="" :datay="multiSubjectData.datay" :isClick="false" :rate="0" :gridLeft="0" :gridRight="0" :gridTop="20" :fontSize="14" :fontColor="'#333333'" :showDataZoom="false" style="height: 270px !important;min-height: 270px !important;"></DifferenceChart>
+                        </div>
+                        <div class="area_module_describe" v-if="multiSubjectData.maxSubject || multiSubjectData.minSubject">
+                            说明:从标准分情况来看,这次考试<template v-if="multiSubjectData.maxSubject"><span style="color: #3ba272">{{ multiSubjectData.maxSubject }}</span>表现突出,请继续保持</template><template v-if="multiSubjectData.minSubject">;<span style="color: #ee6666">{{ multiSubjectData.minSubject }}</span>标准分明显低于其他学科,可能会对总体排名造成影响,可结合错题梳理核心知识点,精准定位薄弱环节,制定针对性的提升计划,以实现各科均衡发展,进一步巩固整体成绩</template>。
+                        </div>
+                        <div class="pring_jg"></div>
+                    </div>
+                    <div class="area_module" v-if="historyExamData.pageNum==page && historyExamData.chartData.length > 0">
+                        <div class="area_module_title">历次标准分追踪分析图</div>
+                        <div class="area_module_chart">
+                            <LineChart v-if="historyExamData.datay.length>0" :datax="historyExamData.datax" :datay="historyExamData.datay" :title="historyExamData.title"
+                                :extraText="false" :showBackground="false" :isShowLabel="true" labelColor="#333333" :legendList="historyExamData.legendList" :tooltipData="historyExamData.tooltipData" :gridLeft="0" :gridRight="0" :gridTop="25" :fontSize="14" :fontColor="'#333333'" :showCheckBox="false" reportHeight="270px" style="min-height:270px!important;">
+                            </LineChart>
+                        </div>
+                        <div class="pring_jg"></div>
+                    </div>
+                    <div class="area_module" v-if="suggestionHtml && multiSuggestionPageNum == page">
+                        <div class="area_module_title">总结建议</div>
+                        <div class="area_module_describe" style="margin-top: 0;" v-html="suggestionHtml"></div>
+                        <div class="pring_jg"></div>
+                    </div>
+                    <!-- 单科 -->
+                    <template v-for="(subject,subKey) in multiSubjectData.singleSubject">
+                        <template v-if="singleSubjectData?.[subKey]?.titlePageNum == page">
+                            <div class="area_header bg_purple">
+                                <img :src="headerLeftIcon" class="header_icon_left" />
+                                {{subject.subjectName}}成绩分析
+                                <img :src="headerRightIcon" class="header_icon_right" />
+                            </div>
+                            <div class="pring_jg"></div>
+                        </template>
+                        <!-- 成绩单 -->
+                        <template v-for="(tableData,index) in (singleSubjectData?.[subKey]?.scrolTableList || [])">
+                            <template v-for="(itemTable,itemIndex) in tableData">
+                                <div class="area_module" v-if="singleSubjectData?.[subKey]?.scrolTablePagesNum?.[index]?.[itemIndex] == page && itemTable.length > 0">
+                                    <div class="area_module_title">成绩单</div>
+                                    <div class="area_module_table">
+                                        <el-table border :data="itemTable" stripe align="center">
+                                            <el-table-column v-for="header in (singleSubjectData?.[subKey]?.scrolHeaderList?.[index]?.[itemIndex] ?? [])" align="center" :label="header.name" min-width="100" show-overflow-tooltip>
+                                                <template slot-scope="scope">
+                                                    <template v-if="header.prop=='score'">
+                                                        <!-- * 1-得分显示分数,小题分显示分数,2-得分显示分数,小题分显示对错
+                                                        * 3-得分显示对错,小题分显示分数,4-得分显示对错,小题分显示对错
+                                                        * 5-得分显示等级,小题分显示分数,6-得分显示等级,小题分显示对错 -->
+                                                        <template v-if="(scope.row.studentOpenness == 3 || scope.row.studentOpenness == 4) && scope.row?.score">
+                                                            <img class="right_or_wrong_icon" v-if="scope.row.fullScore == scope.row.score" src="@/assets/report/score_yes_icon.webp" />
+                                                            <img class="right_or_wrong_icon" v-else-if="scope.row.score === 0" src="@/assets/report/score_no_icon.webp" />
+                                                            <img class="right_or_wrong_icon" v-else src="@/assets/report/score_dimidiate_icon.webp" />
+                                                        </template>
+                                                        <template v-else>{{ scope?.row?.[header.prop] ?? '-' }}</template>
+                                                    </template>
+                                                    <template v-else>{{ scope?.row?.[header.prop] ?? '-' }}</template>
+                                                </template>
+                                            </el-table-column>
+                                        </el-table>
+                                    </div>
+                                </div>
+                                <template v-if="singleSubjectData?.[subKey]?.scrolTablePagesNum?.[index]?.[itemIndex] == page && itemTable.length > 0">
+                                    <div class="pring_jg" style="height: 29px;border-bottom: 1px solid #F3F3F3;box-sizing: border-box;"></div>
+                                    <div class="pring_jg"></div>
+                                </template>
+                            </template>
+                        </template>
+                        <!-- 小题分 大题 知识点  能力要素  自定义分组 -->
+                        <template v-for="group in (singleSubjectData?.[subKey]?.groupQuestionData || [])">
+                            <div class="area_module" v-if="group.chartPagesNum == page && group.type!='smallQuestionData'">
+                                <div class="area_module_title">{{group.groupName}}分析图</div>
+                                <div class="area_module_chart" v-if="group.tableList.length > 0">
+                                    <BarChart v-if="group.type=='bigQuestionData'" :datax="group.datax" :datay="group.datay" unit="" typeName="" :showNuitY="false" :unit="'%'" :showTooltip="false" :isShowMarkLine="false" :gridLeft="0" :gridRight="0" :gridTop="20" :fontSize="14" :fontColor="'#333333'" :showDataZoom="false" style="height: 270px;"></BarChart>
+                                    <RadarCharts v-else :showLegend="false" :reportHeight="`300px`" :showDataLabel="true" :showTooltip="false" :data="group.radarChartData" :unit="'%'" :legendList="[]" legendLeft="center" :showCheckBox="false" :openShowAllLegend="false" :showRadiusAxis="false" :fontSize="14" :fontColor="'#333333'" :style="{height: 'auto',minHeight:'100% !important'}"></RadarCharts>
+                                </div>
+                                <div class="pring_jg"></div>
+                            </div>
+                            <template v-for="(tableData,index) in group.tableList">
+                                <template v-for="(itemTable,itemIndex) in tableData">
+                                    <div class="area_module" v-if="group.tablePagesNum[index][itemIndex] == page && itemTable.length > 0">
+                                        <div class="area_module_title">{{group.groupName}}分析表</div>
+                                        <div class="area_module_table">
+                                            <el-table border :data="itemTable" stripe align="center">
+                                                <el-table-column v-for="header in (group?.staticHeader ?? [])" align="center" :label="header.name" :min-width="header.prop=='smallQuestionNames'?120:90" show-overflow-tooltip>
+                                                    <template slot-scope="scope">
+                                                        {{ scope.row[header.prop] || '-' }}
+                                                    </template>
+                                                </el-table-column>    
+                                                <el-table-column v-for="header in (group?.headerList?.[index]?.[itemIndex] ?? [])" align="center" :label="header.name" :min-width="header.prop=='smallQuestionNames'?120:90" show-overflow-tooltip>
+                                                    <template slot-scope="scope">
+                                                        <template v-if="header.prop == 'score'">
+                                                            <!-- * 1-得分显示分数,小题分显示分数,2-得分显示分数,小题分显示对错
+                                                            * 3-得分显示对错,小题分显示分数,4-得分显示对错,小题分显示对错
+                                                            * 5-得分显示等级,小题分显示分数,6-得分显示等级,小题分显示对错 -->
+                                                            <template v-if="group.studentOpenness == 2 || group.studentOpenness == 4 || group.studentOpenness == 6">
+                                                                <img class="right_or_wrong_icon" v-if="scope.row.fullScore == scope.row.score" src="@/assets/report/score_yes_icon.webp" />
+                                                                <img class="right_or_wrong_icon" v-else-if="scope.row.score == 0" src="@/assets/report/score_no_icon.webp" />
+                                                                <img class="right_or_wrong_icon" v-else src="@/assets/report/score_dimidiate_icon.webp" />
+                                                            </template>
+                                                            <template v-else>{{ scope.row.score || '-' }}</template>
+                                                        </template>
+                                                        <!-- 包含小题 -->
+                                                        <template v-else-if="header.prop == 'smallQuestionNames' && scope?.row?.[header.prop] && Object.prototype.toString.call(scope.row[header.prop]) == '[object Array]'">
+                                                            {{ scope.row[header.prop].join('、') }}
+                                                        </template>
+                                                        <template v-else-if="header.prop=='scoreRateStr'">
+                                                            <template v-if="scope?.row?.scoreRate && !isNaN(scope?.row?.scoreRate)">
+                                                                <span style="color:#EE6666;" v-if="Number(scope?.row?.scoreRate) < 60">{{ scope?.row?.[header.prop] }}</span>
+                                                                <span style="color:#FAC858;" v-else-if="Number(scope?.row?.scoreRate) >= 60 && Number(scope?.row?.scoreRate) < 80">{{ scope?.row?.[header.prop] }}</span>
+                                                                <span style="color:#3BA272;" v-else-if="Number(scope?.row?.scoreRate) >= 80 && Number(scope?.row?.scoreRate) <= 100">{{ scope?.row?.[header.prop] }}</span>
+                                                                <span v-else>{{ scope?.row?.[header.prop] ?? '-'}}</span>
+                                                            </template>
+                                                            <template v-else>{{ scope?.row?.[header.prop] ?? '-'}}</template>
+                                                        </template>
+                                                        <template v-else-if="header.prop == 'difficultyName'">
+                                                            <span :class="GetDifficultyClass(scope.row[header.prop])"></span>
+                                                            <span>{{ GetDifficultyName(scope.row[header.prop]) }}</span>
+                                                        </template>
+                                                        <template v-else>{{ scope.row[header.prop] || '-' }}</template>
+                                                    </template>
+                                                </el-table-column>
+                                            </el-table>
+                                        </div>
+                                    </div>
+                                    <template v-if="group.tablePagesNum[index][itemIndex] == page && itemTable.length > 0">
+                                        <div class="pring_jg" style="height: 29px;border-bottom: 1px solid #F3F3F3;box-sizing: border-box;"></div>
+                                        <div class="pring_jg"></div>
+                                    </template>
+                                </template>
+                            </template>
+                        </template>
+                        <!-- 历次 -->
+                        <div class="area_module" v-if="singleSubjectData?.[subKey]?.historyExamData?.pageNum==page && singleSubjectData[subKey].historyExamData.chartData.length > 0">
+                            <div class="area_module_title">历次标准分追踪分析图</div>
+                            <div class="area_module_chart">
+                                <LineChart v-if="singleSubjectData?.[subKey]?.historyExamData?.datay.length>0" :datax="singleSubjectData[subKey].historyExamData.datax" :datay="singleSubjectData[subKey].historyExamData.datay" :title="singleSubjectData[subKey].historyExamData.title"
+                                    :extraText="false" :showBackground="false" :isShowLabel="true" labelColor="#333333" :legendList="singleSubjectData[subKey].historyExamData.legendList" :tooltipData="singleSubjectData[subKey].historyExamData.tooltipData" :gridLeft="0" :gridRight="0" :gridTop="25" :fontSize="14" :fontColor="'#333333'" :showCheckBox="false" reportHeight="270px" style="min-height:270px!important;">
+                                </LineChart>
+                            </div>
+                            <div class="pring_jg"></div>
+                        </div>
+                        <div class="area_module" v-if="singleSubjectData?.[subKey]?.suggestionHtml && singleSubjectData?.[subKey]?.suggestionPageNum == page">
+                            <div class="area_module_title">总结建议</div>
+                            <div class="area_module_describe" style="margin-top: 0;" v-html="singleSubjectData?.[subKey]?.suggestionHtml"></div>
+                            <div class="pring_jg"></div>
+                        </div>
+                        <!-- 答题卡 -->
+                        <template v-for="(paperItem,paperIndex) in (singleSubjectData?.[subKey]?.paperImageList || [])">
+                            <div class="area_module area_module_img" v-if="singleSubjectData?.[subKey]?.paperImagePageNum?.[paperIndex] == page">
+                                <PaperImage v-if="paperItem.picUrl" :paperImgUrl="paperItem.picUrl" :usedCardType="singleSubjectData?.[subKey]?.usedCardType" :drawData="paperItem.questionVOS || []" :isDrag="false" :isWheel="false" :isShowContextMenu="false" rotateDeg="-90"></PaperImage>
+                            </div>
+                        </template>
+                    </template>
+                    <div class="area_page_number">第 {{ page }} 页</div>
+                </div>
+            </template>
+            <template v-else>
+                <!-- 生成翻书效果 -->
+                <div class="gradient"></div>
+                <img :src="slotProps.content" style="width: 100%;height: 100%;" />
+            </template>
+        </template>
+    </BookFlip>
+</template>
+
+<script>
+import BookFlip from './components/bookFlip.vue';
+import BarChart from "@/views/analysisReport/components/dCharts/barChart"; //单柱状图组件
+import RadarCharts from "@/views/analysisReport/components/dCharts/radarCharts";//G10-G1雷达图
+import DifferenceChart from "@/views/analysisReport/components/dCharts/differenceChart"; //率差图
+import LineChart from "@/views/analysisReport/components/dCharts/lineChart";//折线图
+import PaperImage from '@/components/PaperImage.vue';//答题卡
+import { mapGetters } from "vuex";
+import {getApiName} from '@/utils/common';
+// import { jsPDF } from "jspdf";
+import html2canvas from "html2canvas";
+export default {
+    components: { BookFlip, BarChart, RadarCharts, DifferenceChart, LineChart,PaperImage},
+    data() {
+        return {
+            pageKey:1,
+            headerLeftIcon: require("@/assets/report/header_left_student.webp"),
+            headerRightIcon: require("@/assets/report/header_right_student.webp"),
+            // 书页数据(长度为4,偶数页)
+            colors: this.$global.getScorePerformanceAnalysis(),//按顺序显示的20个颜色值
+            pageCount:15,//页数 第一页不处理
+            printPageHeight:1245,//A4纸高度 去掉边距 40
+            chartHeight:270,//echart 高度
+            moduleHeightData:[],//每个模块的高度
+            modulePageData:[],//每个模块对应的页码
+            stuClasName:'',
+            multiSubjectData: {
+                tablePagesNum:[],
+                tableList:[],
+                headerList:[],
+                staticHeaderData:[],
+                singleSubject:[],
+                standardScoreAnalysisStatus: 0,
+                studentOpenness: '', //学生信息
+                datax: [], //图数据
+                datay: [], //图数据
+                tooltipData: [],
+                maxSubject: '',
+                minSubject: ''
+            },//总分成绩分析
+            historyExamData: {
+                pageNum:'',
+                chartData: [],
+                datax: [],
+                datay: [],
+                title: [],
+                legendList: [],
+                tooltipData:[]
+            }, //总分历次信息(联考)
+            suggestionHtml: null,//总结建议
+            multiSuggestionPageNum:'',//总分 总结建议
+            singleSubjectData:[],//单科数据
+            bookPageImages:[],//生成的图片地址
+            // showReportLoading:false,
+            targetProgress:0,
+            timer:null,
+            loading:true,
+            loadingText:'拼命加载中(1%)……',
+            openLoading:false,
+        };
+    },
+    computed: {
+        reportTitle() {
+            return this?.$store?.state?.report?.examSelectItem?.examName ?? '';
+        },
+        reportParam() {
+            return {
+                examLevel: this.$store.state.report.examLevel, // 任务等级联考还是单校
+                // schoolId: this.$store.state.report.studentReportFilterObject.schoolId, // 学校id
+                // contrastExamIds: this.$store.state.report.lastExamSelectIds, //多次考试任务对比ID,不包含当前任务ID
+                // examId: this.$store.state.report.examId, // 当前考试ID
+                // subjectCode: this.$store.state.report.studentReportFilterObject.subjectCode, //科目code
+                // subjectGroupType: this.$store.state.report.filterObject.subjectGroupType, // 科目是否为组合
+                // isTotal: this.$store.state.report.filterObject.isTotal, //是否为总分科目 1为总分 0为非总分
+                // classIdCode: this.$store.state.report.studentReportFilterObject.classIdCode, // 班级idcode
+                // classGroupName: this.$store.state.report.studentReportFilterObject.classGroupName, // 班级名称
+                // registrationName: this.$store.state.report.studentReportFilterObject.registrationName, // 学籍名称
+                // studentName:this.$store.state.report.studentReportFilterObject.studentName, //学生名称
+                // registrationCode:this.$store.state.report.studentReportFilterObject.registrationCode //学生学籍号
+
+                examLevel: this.$store.state.report.filterObject.examLevel, //1-联考 2-单校
+                contrastExamIds: this.$store.state.report.filterObject.contrastExamIds, //多次考试任务对比ID,不包含当前任务ID
+                examId: this.$store.state.report.filterObject.examId, //考试id
+                subjectCode: this.$store.state.report.filterObject.subjectCode, //科目code
+                subjectGroupType: this.$store.state.report.filterObject.subjectGroupType, //是否为组合科目 1为组合科目 0为非组合科目
+                isTotal: this.$store.state.report.filterObject.isTotal //是否为总分科目 1为总分 0为非总分
+            }
+        },//分析报告公共参数变量
+        ...mapGetters(["userInfo"]),
+        studentName() {
+            return this.userInfo.userName;
+        },
+        schoolType() {
+            return sessionStorage.getItem('schoolType') //1:单校 2:联校
+        },
+    },
+    watch: {
+        // 监听 filterObject 对象变化(注意要开启深度监听)
+        async reportParam() {
+            // clearTimeout(this.timer); // 清除定时器,停止重复执行
+            // this.timer = null;
+            this.PageInit();//初始加载数据
+        },//监听筛选数据变化
+    },
+    async created() {
+        this.setHtmlFontSize();
+        // 监听窗口缩放,实时更新
+        window.addEventListener('resize', this.setHtmlFontSize);
+        // 可选:监听页面加载完成后再设置一次(避免初始渲染问题)
+        window.addEventListener('load', this.setHtmlFontSize);
+        this.PageInit();//页面初始加载数据
+    },
+    destroyed(){
+        // 根组件卸载时销毁监听
+        window.removeEventListener('resize', this.setHtmlFontSize);
+        document.documentElement.style.fontSize = ''; 
+    },
+    methods: {
+        //初始化设置
+        setHtmlFontSize(){
+            // 设计稿宽度(根据实际设计稿调整,例如 1920px)
+            const designWidth = 1920;
+            // 设计稿中 1rem 对应的 px 值(例如 16px)
+            const baseFontSize = 16;
+
+            // 获取当前浏览器窗口宽度
+            const windowWidth = document.documentElement.clientWidth || window.innerWidth;
+
+            // 计算当前窗口下的 html font-size(按设计稿比例缩放)
+            const htmlFontSize = (windowWidth / designWidth) * baseFontSize;
+            // 应用到 html 元素
+            document.documentElement.style.fontSize = `${htmlFontSize}px`;
+        },
+        async PageInit() {
+            // let currentProgress = 1;
+            // this.loadingText = `拼命加载中(1%)……`;
+            // this.timer = setInterval(() => {
+            //     currentProgress++;
+            //     this.loadingText = `拼命加载中(${currentProgress}%)……`;
+            // }, 500);
+            this.loading = true;
+            this.openLoading = false;
+            this.bookPageImages = [];
+            // this.targetProgress = 1;
+            // this.showReportLoading = true;
+            this.pageCount = 15;
+            this.$nextTick(()=>{
+                const titleHeight = this.$refs?.areaReportTitle?.offsetHeight || 0;
+                const reportTitleHeight = titleHeight + 82;
+                this.moduleHeightData = [reportTitleHeight];//每个模块的高度 初始标题高度
+            })
+            this.modulePageData = [1];
+            this.multiSuggestionPageNum = '';//总分 总结建议
+            this.singleSubjectData = [];//单科数据
+            this.pageKey += 1;
+            //多科成绩总览 科目标准分分析
+            await this.QueryMultiSubjectData();
+            //学生端查询总分,多科历次信息(联考)
+            await this.QueryHistoryExamData();
+            // 学生端查询总分,多科总结建议信息(联考)
+            await this.QuerySuggestionData();
+            let index = 0;
+            for (const element of this.multiSubjectData.singleSubject){
+                //单科每个模块标题分页
+                this.singleSubjectData.push({
+                    titlePageNum:this.SingleChartPage(82),
+                    scrolTablePagesNum:[],//成绩单
+                    scrolTableList:[],
+                    scrolHeaderList:[],
+                    groupQuestionData:[],//小题分组、大题分组、知识点、自定义分组
+                    historyExamData:{
+                        pageNum:'',
+                        chartData: [],
+                        datax: [],
+                        datay: [],
+                        title: [],
+                        legendList: [],
+                        tooltipData:[]
+                    },
+                    suggestionPageNum:'',//总结建议分页
+                    suggestionHtml: '',//总结建议
+                    paperImageList:[],//答题卡
+                    paperImagePageNum:[],//答题卡页码
+                })
+                await this.QueryOneSubjectData(element.subjectCode,index);
+                await this.QueryOneSubjectSmallQuestionData(element.subjectCode,index);
+                await this.QueryOneSubjectGroupQuestionData(element.subjectCode,index);
+                await this.QueryOneSubjectCustomGroupQuestion(element.subjectCode,index);
+                //学生端查询单科-历次查询(联考)
+                await this.QueryOneSubjectHistoryExamData(element.subjectCode,index);
+                await this.QueryOneSubjectSuggestionData(element.subjectCode,index);
+                await this.FindStudentCard(element.subjectCode,index);
+                index++; // 每次循环后索引+1
+            }
+            this.pageCount = this.modulePageData.length > 0 ? Math.max(...this.modulePageData) : 1;
+            this.loading = false;
+            console.log(this.pageCount,this.modulePageData,this.moduleHeightData,777777)
+            // this.$nextTick(()=>{
+            //     setTimeout(()=>{
+            //         // clearTimeout(this.timer); // 清除定时器,停止重复执行
+            //         // this.GetPageImages(currentProgress);
+            //         this.GetPageImages();
+            //     },2000)
+            // })
+        },
+        //下载预览图片
+        OpenBookImages(){
+            if(this.bookPageImages.length){
+                this.$refs.bookFlipBox.showFullScreen()
+            }else{
+                this.openLoading = true;
+                this.$nextTick(()=>{
+                    setTimeout(()=>{
+                        this.GetPageImages();
+                    },2000)
+                })
+            }
+        },
+        //获取图片
+        async GetPageImages(){
+            //获取所有的area_page 元素
+            const elements = document.querySelectorAll(".web_mode .web_area_page");
+            // const stepLens = elements?.length || 1;
+            // const remainingProgress = 100 - progress;//剩余进度
+            // const perStepProgress = Math.floor(remainingProgress / stepLens) || 1; // 每次增加的进度 向下取整
+            // const pdf = new jsPDF("p", "pt", "a4"); // 'p'表示纵向,'a4'表示A4纸张尺寸
+            // const pdfWidth = pdf.internal.pageSize.getWidth(); //获取pdf的宽度
+            // const pdfHeight = pdf.internal.pageSize.getHeight(); //获取pdf的高度
+            // let yPos = 0; //当前图像在pdf页面上的垂直位置的变量
+
+            let i = 1;
+            // let currentProgress = progress;
+            for (const element of elements) {
+                await html2canvas(element, {
+                    scale: 2, // 增加缩放比例
+                    useCORS: true, // 允许跨域
+                    logging: false, // 是否打印调试日志(开发时开启,生产关闭)
+                    letterRendering: true, // 文字抗锯齿 启用字母渲染
+                }).then(async (canvas) => {
+                    // const imgData = canvas.toDataURL("image/png");
+                    const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
+                    const imgUrl = URL.createObjectURL(blob);
+                    console.log(imgUrl,89999)
+                    this.bookPageImages.push(imgUrl);
+                    // currentProgress  = Math.min(Math.round((i / stepLens) * 100),100);
+                    // currentProgress  = Math.min(progress + i * perStepProgress,100);
+                    // this.targetProgress = currentProgress
+                    // this.loadingText = `拼命加载中(${currentProgress}%)……`;
+
+
+                    // const imgProps = pdf.getImageProperties(imgData); // 获取图像的属性,包括宽度和高度
+                    // const imgWidth = imgProps.width;
+                    // const imgHeight = imgProps.height;
+                    // const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight); //计算图片的缩放比例
+                    // const adjustWidth = imgWidth * ratio;
+                    // const adjustHeight = imgHeight * ratio;
+
+                    // // 在添加每个图像之前,检查当前页面的高度是否足够。如果不够,则添加新页面,并将 yPos 重置为 0
+                    // if (yPos + adjustHeight > pdfHeight) {
+                    //     pdf.addPage();
+                    //     yPos = 0;
+                    // }
+
+                    // // 将图像添加到pdf中
+                    // pdf.addImage(imgData, "PNG", 0, yPos, adjustWidth, adjustHeight);
+                    // yPos += adjustHeight;
+
+                    // // 如果添加图像后剩余空间不足一页,则添加新页面
+                    // if (yPos > pdfHeight) {
+                    //     pdf.addPage();
+                    //     yPos = 0;
+                    // }
+
+
+                    i++;
+                })
+            }
+            // if(currentProgress < 100){
+            //     this.loadingText = `拼命加载中(100%)……`;
+            // }
+            // 保存pdf文件
+            // pdf.save("联校报告册.pdf");
+            // this.showReportLoading = false;
+            setTimeout(()=>{
+                // this.loading = false;
+                this.openLoading = false;
+                this.$refs.bookFlipBox.showFullScreen()
+            },500)
+        },
+        //总分成绩分析
+        async QueryMultiSubjectData() {
+            await this.$api.reportStudent[getApiName()].queryMultiSubjectData({
+                ...this.reportParam,
+                subjectGroupType: 1, // 科目是否为组合
+                isTotal: 1, //是否为总分科目 1为总分 0为非总分
+            }).then(res => {
+                if (res.code == 200 && res.data) {
+                    const titleData = res?.data?.titleData || [];
+                    this.multiSubjectData.studentOpenness = res?.data?.studentOpenness ?? '';
+                    const headerData = titleData.filter(item=>item.prop!='imgUrlList' && item.prop!='subjectName');
+                    const staticHeaderData = titleData.filter(item=>item.prop=='subjectName');
+                    const tableData = res?.data?.tableData || [];
+                    this.stuClasName = tableData?.find(item=>item.subjectCode==0)?.className || '';
+                    //表格分页
+                    const pageTableData = this.TableRowAndColumnPage(headerData,tableData,7,1);//8列一个表
+                    this.multiSubjectData.tablePagesNum = pageTableData.tablePagesNum;
+                    this.multiSubjectData.tableList = pageTableData.tableList;
+                    this.multiSubjectData.headerList = pageTableData.headerList;
+                    this.multiSubjectData.staticHeaderData = staticHeaderData;//静态表头
+
+                    //标准分分析图
+                    this.multiSubjectData.datax = []
+                    this.multiSubjectData.datay = []
+                    this.multiSubjectData.tooltipData = []
+                    this.multiSubjectData.standardScoreAnalysisStatus = res?.data?.standardScoreAnalysisStatus ?? 1;
+                    //总分
+                    const totalScore = tableData.filter(item => item.isTotal == 1)
+                    //组合
+                    const subjectGroup = tableData.filter(
+                        item => item.isTotal == 0 && item.subjectGroupType == 1
+                    )
+                    //单科
+                    const singleSubject = tableData.filter(
+                        item => item.isTotal == 0 && item.subjectGroupType == 0
+                    )
+                    this.multiSubjectData.singleSubject = singleSubject;//单科
+                    const chartData = [...totalScore, ...subjectGroup, ...singleSubject];
+                    let datax = [],datay = [];
+                    chartData.forEach(item => {
+                        this.multiSubjectData.datax.push(item?.subjectName ?? '-')
+                        this.multiSubjectData.datay.push(!item?.standardScore || item?.standardScore == '-' ? 0 : item?.standardScore)
+                        if (item.isTotal == 0 && item.subjectGroupType == 0) {
+                            datax.push(item?.subjectName ?? '')
+                            datay.push(item?.standardScore ?? 0)
+                        }
+                    })
+                    let maxSubject = [],minSubject = []
+                    datay.forEach((item, k) => {
+                        if (Number(item) > 0) {
+                            maxSubject.push(datax[k])
+                        }
+                        if (Number(item) < 0) {
+                            minSubject.push(datax[k])
+                        }
+                    })
+                    this.multiSubjectData.maxSubject = maxSubject.join('、')
+                    this.multiSubjectData.minSubject = minSubject.join('、')
+                    //标准分分析图分页
+                    this.$nextTick(()=>{
+                        if((this.multiSubjectData.standardScoreAnalysisStatus === 0 && this.schoolType == 2) || this.schoolType == 1){
+                            const desHeight = this.$refs?.standardScoreChartDes?.offsetHeight || 0;
+                            const divHeight = 404 + desHeight;
+                            this.multiSubjectData.chartPagesNum = this.SingleChartPage(this.multiSubjectData.datay.length > 0 ? divHeight : 0);
+                        }else{
+                            this.multiSubjectData.chartPagesNum = this.SingleChartPage(0);
+                        }
+                        // console.log(divHeight,desHeight,this.multiSubjectData.chartPagesNum,13322133)
+                    })
+                } else {
+                    this.multiSubjectData.studentOpenness = '';
+                    this.multiSubjectData.tablePagesNum = [];
+                    this.multiSubjectData.tableList = [];
+                    this.multiSubjectData.headerList = [];
+                    this.multiSubjectData.singleSubject = [];
+                    // 标准分分析
+                    this.multiSubjectData.datax = []
+                    this.multiSubjectData.datay = []
+                    this.multiSubjectData.tooltipData = []
+                    this.multiSubjectData.standardScoreAnalysisStatus = 1;
+                    this.multiSubjectData.maxSubject = []
+                    this.multiSubjectData.minSubject = []
+                    this.multiSubjectData.chartPagesNum = '';//标准分分析图分页
+                }
+            })
+        },
+        //总分,多科历次信息
+        async QueryHistoryExamData() {
+            await this.$api.reportStudent[getApiName()].queryHistoryExamData({
+                ...this.reportParam,
+                subjectGroupType: 1, // 科目是否为组合
+                isTotal: 1, //是否为总分科目 1为总分 0为非总分
+            }).then(res => {
+                if (res.code == 200 && res.data) {
+                    const detailData = (res.data?.detailData || []).reverse();
+                    this.historyExamData.chartData = detailData;
+                    const selectNames = res.data.selectNames || [];
+                    const selectVal = selectNames?.[0]?.prop ?? '';
+                    this.historyExamData.datax = [];
+                    let datay = [],tooltipData = [];
+                    detailData.forEach(item => {
+                        this.historyExamData.datax.push(item.examName)
+                        datay.push(item[selectVal])
+                        tooltipData.push({
+                            name: selectNames?.[0]?.name ?? '',
+                            value: item[selectVal]
+                        })
+                    })
+                    this.historyExamData.datay = [datay];
+                    this.historyExamData.tooltipData = [tooltipData];
+                    this.historyExamData.pageNum = this.SingleChartPage(this.historyExamData.chartData.length > 0 ? 363 : 0);
+                } else {
+                    this.historyExamData.chartData = [];
+                    this.historyExamData.tooltipData = [];
+                    this.historyExamData.datax = [];
+                    this.historyExamData.datay = [];
+                    this.historyExamData.pageNum = '';
+                }
+            })
+        },
+        // 学生端查询总分,多科总结建议信息(联考)
+        async QuerySuggestionData() {
+            await this.$api.reportStudent[getApiName()].querySuggestionData({
+                ...this.reportParam,
+                subjectGroupType: 1, // 科目是否为组合
+                isTotal: 1, //是否为总分科目 1为总分 0为非总分
+            }).then(res => {
+                if (res.code == 200 && res.data) {
+                    const data = res.data;
+                    const upSubjectList = data.upSubjectData || [];
+                    const downSubjectList = data.downSubjectData || [];
+                    const upSubjectData = upSubjectList.map(item => {
+                        if (data.studentOpenness == 1 || data.studentOpenness == 2) {
+                            return `${item.subjectName}(得分${item.score})`
+                        } else {
+                            return `${item.subjectName}(${item.score})`
+                        }}).join('、')
+                    const downSubjectData = downSubjectList.map(item => {
+                        if (data.studentOpenness == 1 || data.studentOpenness == 2) {
+                            return `${item.subjectName}(得分${item.score})`
+                        } else {
+                            return `${item.subjectName}(${item.score})`
+                        }}).join('、')
+                    if(data?.fullScore){
+                        const scoreText = data?.studentOpenness == 1 || data?.studentOpenness == 2 ? '总分' : '总分标准分为'
+                        const unit = data?.studentOpenness == 1 || data?.studentOpenness == 2 ? '分' : '';
+                        this.suggestionHtml = `${data?.studentName || ''}同学,本次考试${scoreText}<span style="color: #2e64fa">${data?.fullScore}</span>${unit},整体处于${data?.summarySuggestionLevel},`;
+                        if(upSubjectData){
+                            this.suggestionHtml += `<span style="color: #3ba272">${upSubjectData}</span>是你的优势学科,建议通过提分练习进行强化,继续保持这类学科的优势性${downSubjectData ? ';' : '。'}`;
+                        }
+                        if(downSubjectData){
+                            this.suggestionHtml += `<span style="color: #f56c6c">${downSubjectData}</span>是你的劣势学科,建议先加强学习,熟练掌握薄弱知识点的基础,然后通过提分练习进行巩固和强化,争取下次考试获得更优异的成绩!`;
+                        }
+                    } 
+                } else {
+                    this.suggestionHtml = null
+                }
+                this.$nextTick(()=>{
+                    const suggestionHeight = this.$refs?.multiSuggestionModule?.offsetHeight || 0;
+                    // console.log(suggestionHeight,99888)
+                    this.multiSuggestionPageNum = this.SingleChartPage(this.suggestionHtml?suggestionHeight:0);
+                })
+            })
+        },
+        //学生端查询单科-我的成绩
+        async QueryOneSubjectData(subjectCode,index) {
+            await this.$api.reportStudent[getApiName()].queryOneSubjectData({
+                ...this.reportParam,
+                subjectGroupType: 0, // 科目是否为组合
+                isTotal: 0, //是否为总分科目 1为总分 0为非总分
+                subjectCode:subjectCode
+            }).then(res => {
+                if (res.code == 200 && res.data) {
+                    const headData = [{
+                        name:'班级',
+                        prop:'className',
+                        display:true
+                    },{
+                        name:'原始分',
+                        prop:'score',
+                        display:res?.data?.scoreStatus === 0 //0-显示,1-不显示
+                    },{
+                        name:'赋分',
+                        prop:'rateScore',
+                        display:res?.data?.rateScoreStatus === 0 //0-显示,1-不显示
+                    },{
+                        name:'班排',
+                        prop:'classRank',
+                        display:res?.data?.classRankStatus === 0
+                    },{
+                        name:'年排',
+                        prop:'schoolRank',
+                        display:res?.data?.schoolRankStatus === 0
+                    },{
+                        name:'联排',
+                        prop:'examRank',
+                        display:res?.data?.examRankStatus === 0 && this.reportParam.examLevel == 1 && this.schoolType == 2
+                    },{
+                        name:'赋分等级',
+                        prop:'rateScoreName',
+                        display:res?.data?.rateScoreStatus === 0
+                    },{
+                        name:'标准分',
+                        prop:'standardScore',
+                        display:(res?.data?.standardScoreStatus === 0 && this.schoolType == 2) || this.schoolType == 1
+                    },{
+                        name:'学业等级',
+                        prop:'gradeName',
+                        display:(res?.data?.gradeNameStatus === 0 && this.schoolType == 2) || this.schoolType == 1
+                    },{
+                        name:'得分率',
+                        prop:'scoreRate',
+                        display:res?.data?.scoreRateStatus === 0
+                    },{
+                        name:'班级最高分',
+                        prop:'classMaxScore',
+                        display:res?.data?.classMaxScoreStatus === 0 && this.schoolType == 2
+                    },{
+                        name:'年级最高分',
+                        prop:'gradeMaxScore',
+                        display:res?.data?.gradeMaxScoreStatus === 0 && this.schoolType == 2
+                    },{
+                        name:'联校最高分',
+                        prop:'examMaxScore',
+                        display:res?.data?.examMaxScoreStatus === 0 && this.schoolType == 2
+                    },{
+                        name:'班级均分',
+                        prop:'classAvgScore',
+                        display:res?.data?.classAvgScoreStatus === 0 && this.schoolType == 2
+                    },{
+                        name:'年级均分',
+                        prop:'gradeAvgScore',
+                        display:res?.data?.gradeAvgScoreStatus === 0 && this.schoolType == 2
+                    },{
+                        name:'联校均分',
+                        prop:'examAvgScore',
+                        display:res?.data?.examAvgScoreStatus === 0 && this.schoolType == 2
+                    }] 
+                    const tableData = [res.data] || [];
+                    const headerList = headData.filter(item=>item.display);
+                    const pageTableData = this.TableRowAndColumnPage(headerList,tableData,8,1);//8列一个表
+                    this.singleSubjectData[index].scrolTablePagesNum = pageTableData.tablePagesNum;
+                    this.singleSubjectData[index].scrolTableList = pageTableData.tableList;
+                    this.singleSubjectData[index].scrolHeaderList = pageTableData.headerList;//表头
+                } else {
+                    this.singleSubjectData[index].scrolTablePagesNum = [];
+                    this.singleSubjectData[index].scrolTableList = [];
+                    this.singleSubjectData[index].scrolHeaderList = [];//表头
+                }
+            })
+        },
+        //学生端查询单科-小题分析(表格-图表)
+        async QueryOneSubjectSmallQuestionData(subjectCode,index) {
+            await this.$api.reportStudent[getApiName()].queryOneSubjectSmallQuestionData({
+                ...this.reportParam,
+                subjectGroupType: 0, // 科目是否为组合
+                isTotal: 0, //是否为总分科目 1为总分 0为非总分
+                subjectCode:subjectCode
+            }).then(res => {
+                if (res.code == 200 && res?.data?.tableData?.length > 0) {
+                    const tableData = res.data.tableData || [];
+                    const titleData = res.data.titleData || [];
+                    const studentOpenness = res.data?.studentOpenness ?? '';//控制得分显示对错或值
+                    this.TableChartData(tableData, titleData, studentOpenness, index,'smallQuestionData','小题');
+                }
+            })
+        },
+        //学生端查询单科-大题分析,知识点分析,能力要素分析(联考)
+        async QueryOneSubjectGroupQuestionData(subjectCode,index) {
+            await this.$api.reportStudent[getApiName()].queryOneSubjectGroupQuestionData({
+                ...this.reportParam,
+                subjectGroupType: 0, // 科目是否为组合
+                isTotal: 0, //是否为总分科目 1为总分 0为非总分
+                subjectCode:subjectCode
+            }).then(res => {
+                if (res.code == 200 && res.data) {
+                    const { bigQuestion, knowledgePointQuestion, abilityQuestion } = res.data
+                    if (bigQuestion && bigQuestion?.tableData?.length > 0) {
+                        const tableData = bigQuestion.tableData || [];
+                        const titleData = bigQuestion.titleData || [];
+                        const studentOpenness = bigQuestion?.studentOpenness ?? '';
+                        this.TableChartData(tableData, titleData, studentOpenness, index,'bigQuestionData','大题');
+                    }
+                    if (knowledgePointQuestion && knowledgePointQuestion?.tableData?.length > 0) {
+                        const tableData = knowledgePointQuestion.tableData || [];
+                        const titleData = knowledgePointQuestion.titleData || [];
+                        const studentOpenness = knowledgePointQuestion?.studentOpenness ?? '';
+                        this.TableChartData(tableData, titleData, studentOpenness, index,'knowledgePointQuestionData','知识点');
+                    }
+                    if (abilityQuestion && abilityQuestion?.tableData?.length > 0) {
+                        const tableData = abilityQuestion.tableData || [];
+                        const titleData = abilityQuestion.titleData || [];
+                        const studentOpenness = abilityQuestion?.studentOpenness ?? '';
+                        this.TableChartData(tableData, titleData, studentOpenness, index,'abilityQuestionData','能力要素');
+                    }
+                }
+            })
+        },
+        //学生端查询单科-自定义分组(联考)
+        async QueryOneSubjectCustomGroupQuestion(subjectCode,index) {
+            await this.$api.reportStudent[getApiName()].queryOneSubjectCustomGroupQuestion({
+                ...this.reportParam,
+                subjectGroupType: 0, // 科目是否为组合
+                isTotal: 0, //是否为总分科目 1为总分 0为非总分
+                subjectCode:subjectCode
+            }).then(res => {
+                if (res.code == 200 && res.data) {
+                    const customQuestionData = res?.data?.customQuestionData ?? []
+                    if (customQuestionData && customQuestionData.length > 0) {
+                        customQuestionData.forEach((item, key) => {
+                            const tableData = item?.questionData?.tableData || []
+                            const titleData = item?.questionData?.titleData || []
+                            const studentOpenness = res.data?.studentOpenness ?? ''
+                            this.TableChartData(tableData, titleData, studentOpenness, index,`customQuestionData${key}`,item.customName);
+                        })
+                    }
+                }
+            })
+        },
+        //学生端查询单科-历次查询(联考)
+        async QueryOneSubjectHistoryExamData(subjectCode,index) {
+            await this.$api.reportStudent[getApiName()].queryOneSubjectHistoryExamData({
+                ...this.reportParam,
+                subjectGroupType: 0, // 科目是否为组合
+                isTotal: 0, //是否为总分科目 1为总分 0为非总分
+                subjectCode:subjectCode
+            }).then(res => {
+                if (res.code == 200 && res.data) {
+                    const detailData = (res.data?.detailData || []).reverse();
+                    // console.log(this.singleSubjectData[index],98899)
+                    this.singleSubjectData[index].historyExamData.chartData = detailData;
+                    const selectNames = res.data.selectNames || [];
+                    const selectVal = selectNames?.[0]?.prop ?? '';
+                    this.singleSubjectData[index].datax = [];
+                    let datay = [],tooltipData = [];
+                    detailData.forEach(item => {
+                        this.singleSubjectData[index].historyExamData.datax.push(item.examName)
+                        datay.push(item[selectVal])
+                        tooltipData.push({
+                            name: selectNames?.[0]?.name ?? '',
+                            value: item[selectVal]
+                        })
+                    })
+                    this.singleSubjectData[index].historyExamData.datay = [datay];
+                    this.singleSubjectData[index].historyExamData.tooltipData = [tooltipData];
+                    this.singleSubjectData[index].historyExamData.pageNum = this.SingleChartPage(this.singleSubjectData[index].historyExamData.chartData.length > 0 ? 363 : 0);
+                }
+            })
+        },
+        //学生端查询单科-总结建议
+        async QueryOneSubjectSuggestionData(subjectCode,index) {
+            await this.$api.reportStudent[getApiName()].queryOneSubjectSuggestionData({
+                ...this.reportParam,
+                subjectGroupType: 0, // 科目是否为组合
+                isTotal: 0, //是否为总分科目 1为总分 0为非总分
+                subjectCode:subjectCode
+            }).then(res => {
+                if (res.code == 200 && res.data) {
+                    const data = res.data
+                    //* 1-得分显示分数,小题分显示分数,2-得分显示分数,小题分显示对错
+                    //* 3-得分显示对错,小题分显示分数,4-得分显示对错,小题分显示对错
+                    //* 5-得分显示等级,小题分显示分数,6-得分显示等级,小题分显示对错
+                    const upSubjectList = data?.upSubjectData || [];
+                    const downSubjectList = data?.downSubjectData || [];
+                    const upSubjectData = upSubjectList.map(item => {
+                        if (data.studentOpenness == 1 || data.studentOpenness == 2) {
+                            return `${item.subjectName}得分<span style="color: #3BA272;">${item.score}</span>分`
+                        } else {
+                            return `${item.subjectName}标准分为<span style="color: #3BA272;">${item.score}</span>`
+                        }
+                    }).join('、')
+                    const downSubjectData = downSubjectList.map(item => {
+                        if (data.studentOpenness == 1 || data.studentOpenness == 2) {
+                            return `${item.subjectName}得分<span style="color: #EE6666;">${item.score}</span>分`
+                        } else {
+                            return `${item.subjectName}标准分为<span style="color: #EE6666;">${item.score}</span>`
+                        }
+                    }).join('、')
+                    this.singleSubjectData[index].suggestionHtml = '';
+                    if (data.studentName && (upSubjectData || downSubjectData)) {
+                        this.singleSubjectData[index].suggestionHtml = `${data.studentName}同学,本次考试`
+                    }
+                    if (upSubjectData) {
+                        this.singleSubjectData[index].suggestionHtml += `${upSubjectData},是你的优势学科,建议通过提分练习进行强化,继续保持这类学科的优势性!`
+                    }
+                    if (downSubjectData) {
+                        this.singleSubjectData[index].suggestionHtml += `${downSubjectData},是你的劣势学科,建议先加强学习,熟练掌握薄弱知识点的基础,然后通过提分练习进行巩固和强化,争取下次考试获得更优异的成绩!`
+                    }
+                    this.$nextTick(()=>{
+                        const suggestionHeight = this.$refs?.[`singleSubjectSuggestion_${index}`]?.[0]?.offsetHeight || 0;
+                        // console.log(suggestionHeight,8999111)
+                        this.singleSubjectData[index].suggestionPageNum = this.SingleChartPage(this.singleSubjectData[index].suggestionHtml?suggestionHeight:0);
+                    })
+                } else {
+                    this.singleSubjectData[index].suggestionHtml = ''
+                }
+            })
+        },
+        //答题卡
+        async FindStudentCard(subjectCode,index){
+            await this.$api.reportStudent[getApiName()].findStudentCard({
+                examId:this.reportParam.examId,
+                subjectCode:subjectCode,
+                registrationCode:this.reportParam.registrationCode
+            }).then(res => {
+                if (res.code == 200 && res.data) {
+                    let paperImageList = res.data.pageVOS || [];
+                    //先添加总分数据
+                    let totalScore = {
+                        questionName: '总分',
+                        fullScore: res?.data?.fullScore || '',
+                        score: res?.data?.levelName ?? res?.data?.totalScore,
+                        displayType: res.data.displayType, //显示类型 0-分数 1-对错 2-等级
+                        displayName: res.data.displayName, //显示值
+                        correctType: res.data.correctType, //显示对错的时候 0-错 1-半对 2-全对
+                        questionAnswer: '',
+                        answer: '',
+                        samplingPosition: '{"x":195,"y":247,"page":1}'
+                    }
+                    if (paperImageList.length > 0 && paperImageList[0].questionVOS) {
+                        paperImageList[0].questionVOS.unshift(totalScore)
+                    }
+                    this.singleSubjectData[index].paperImageList = paperImageList;
+                    this.singleSubjectData[index].usedCardType = res?.data?.usedCardType ?? 1;
+                    for(let i = 0;i<paperImageList.length;i++){
+                        const pageNum = this.SingleChartPage(this.printPageHeight);
+                        this.singleSubjectData[index].paperImagePageNum.push(pageNum);
+                    }
+                }
+            })
+        },
+        //处理数据
+        TableChartData(tableData, titleData, studentOpenness, index,type,groupName) {
+            //柱状图
+            let datax = [],datay = [],radarChart = []
+            tableData.forEach(item => {
+                datax.push(item.questionName)
+                const scoreRate = item?.scoreRate ?? ''
+                datay.push(scoreRate)
+                radarChart.push([item.questionName, scoreRate])
+            })
+            //雷达图
+            const radarChartData = [['group', '得分率'], ...radarChart]
+            let chartHeight = 0;//小题分析只显示table 不显示图
+            if(type=='bigQuestionData'){
+                chartHeight = 363 //柱状图
+            }else if (type == 'smallQuestionData'){
+                chartHeight = 0
+            } else{
+                chartHeight = 393//雷达图
+            }
+            const chartPagesNum = this.SingleChartPage(chartHeight);
+            const staticHeader = titleData.slice(0,1);//固定表头
+            const dynamicHeader = titleData.slice(1);//动态分组标头
+            
+            const pageTableData = this.TableRowAndColumnPage(dynamicHeader,tableData,7,1);//8列一个表
+            const tablePagesNum = pageTableData.tablePagesNum;
+            const tableList = pageTableData.tableList;
+            const headerList = pageTableData.headerList;//表头
+
+            this.singleSubjectData[index].groupQuestionData.push({
+                type:type,
+                groupName:groupName,
+                studentOpenness:studentOpenness,
+                staticHeader:staticHeader,
+                headerList:headerList,
+                tableList:tableList,
+                tablePagesNum:tablePagesNum,
+                chartPagesNum:chartPagesNum,
+                datax:datax,
+                datay:datay,
+                radarChartData:radarChartData
+            })
+        },
+        //按照原始顺序累加,当总和超过A4高度1285时,将当前元素放入下一组
+        groupByThreshold(arr, threshold) {
+            const result = [];
+            let currentGroup = [];
+            let currentSum = 0;
+
+            for (let i = 0; i < arr.length; i++) {
+                const num = arr[i];
+
+                // 如果当前组为空,直接添加
+                if (currentGroup.length === 0) {
+                    currentGroup.push(num);
+                    currentSum += num;
+                }
+                // 如果加上当前元素会超过阈值,则创建新组
+                else if (currentSum + num > threshold) {
+                    result.push(currentGroup);
+                    currentGroup = [num];
+                    currentSum = num;
+                }
+                // 否则添加到当前组
+                else {
+                    currentGroup.push(num);
+                    currentSum += num;
+                }
+            }
+
+            // 添加最后一组
+            if (currentGroup.length > 0) {
+                result.push(currentGroup);
+            }
+
+            return result;
+        },
+        /*
+        *对表格横向和竖向分页
+        *dynamicHeader 动态表头
+        *tableData 表格数据
+        *groupSize 动态表头分页大小
+        *headNum 表头行数
+        */
+        TableRowAndColumnPage(dynamicHeader,tableData,groupSize,headNum){
+            /*
+            * 每个模块的高度
+            */
+            const tableTitle = 49;//表格标题高度
+            const tableHeadHeight = 41;//表头高度
+            const tableGap = 49;//表格和图表之间的间距
+
+            //对表格横向分页
+            const groupDynamicHeader = [];
+            for (let i = 0; i < dynamicHeader.length; i += groupSize) {
+                const group = dynamicHeader.slice(i, i + groupSize);
+                //表格补充
+                if(group.length > 0 && group.length!=groupSize && i > 0){
+                    const tdLens = groupSize - group.length;
+                    for(let j = 0; j < tdLens; j ++){
+                        group.push({})
+                    }
+                }
+                groupDynamicHeader.push(group)
+            }
+            let tablePagesNum = [],tableList = [],headerList = [];//当前模块所有table页码 表格  表格高度
+            for (let i = 0; i < groupDynamicHeader.length; i ++) {
+                let itemTablePagesNum = [],itemTableList = [],itemHeadder= [], moduleHeightData = [] ,isShowSplitLine = [];//每个table的页码 表格  表格高度 isShowSplitLine:表格是否显示分割线
+                //计算最后一页模块的高度
+                let lastPageDataSum = 0;
+                const lastPageNum = this.modulePageData.length > 0 ? this.modulePageData[this.modulePageData.length - 1] : 1;//最后一页页码
+                this.modulePageData.forEach((item,p)=>{
+                    if(item == lastPageNum){
+                        lastPageDataSum += this.moduleHeightData[p];
+                    }
+                })
+                const remainHeight = this.printPageHeight - lastPageDataSum - (tableHeadHeight * headNum) - tableTitle - 41;//41 是距离底部的边距 
+                const remainTableRow = Math.floor(remainHeight / 40);//剩余可放多少行
+                if(remainTableRow > 1){
+                    const tableLens = tableData.length ?? 0;//表格行数量
+                    if(tableLens > remainTableRow){//判断第二页是否有值
+                        const pageTableHeight = this.printPageHeight - (tableHeadHeight * headNum) - tableTitle - 41;// 一页能放下的table的高度  41 是距离底部的边距 
+                        const tableRowNum = Math.floor(pageTableHeight / 40);//一页可放多少行
+                        //第一页数据
+                        const firstTableData = tableData.slice(0,remainTableRow);
+                        itemTableList.push(firstTableData)
+                        itemHeadder.push(groupDynamicHeader[i])
+                        isShowSplitLine.push(false);//是否显示表格底部的分割线 第一页不显示
+                        //第一页页码
+                        // this.modulePageData.push(lastPageNum);
+                        //当前表格第一页页码
+                        itemTablePagesNum.push(lastPageNum);//图表分页页码
+                        // 第一页高度
+                        const tableHeight1 = firstTableData.length > 0 ? (tableHeadHeight * headNum) + tableTitle + tableGap + firstTableData.length * 40 : 0;
+                        moduleHeightData.push(tableHeight1)
+                        const otherTableData = tableData.slice(remainTableRow);//接取完上一页剩下的表格数据
+                        // 按步长遍历,每次截取 表格行数 元素
+                        for (let j = 0; j < otherTableData.length; j += tableRowNum) {
+                            const group = otherTableData.slice(j, j + tableRowNum);
+                            const key = j/tableRowNum;
+                            //表格数据
+                            itemTableList.push(group);
+                            itemHeadder.push(groupDynamicHeader[i])
+                            isShowSplitLine.push(false);//是否显示表格底部的分割线
+                            //页码
+                            // this.modulePageData.push(lastPageNum + key + 1);
+                            //当前表格分页页码
+                            itemTablePagesNum.push(lastPageNum + key + 1);
+                            //高度
+                            const tableHeight = group.length > 0 ? (tableHeadHeight * headNum) + tableTitle + tableGap + group.length * 40 : 0;
+                            moduleHeightData.push(tableHeight)
+                        }
+                    }else{
+                        const tableData1 = tableData.slice(0,remainTableRow);
+                        const tableData2 = tableData.slice(remainTableRow);
+                        itemTableList.push(tableData1,tableData2);
+                        itemHeadder.push(groupDynamicHeader[i],groupDynamicHeader[i])
+                        // this.modulePageData.push(lastPageNum,lastPageNum);
+                        //页码
+                        itemTablePagesNum.push(lastPageNum,lastPageNum)
+                        //高度
+                        const tableHeight1 = tableData1.length > 0 ? (tableHeadHeight * headNum) + tableTitle + tableGap + tableData1.length * 40 : 0;
+                        const tableHeight2 = tableData2.length > 0 ? (tableHeadHeight * headNum) + tableTitle + tableGap + tableData2.length * 40 : 0;
+                        moduleHeightData.push(tableHeight1,tableHeight2)
+                    }
+                }else{
+                    const pageTableHeight = this.printPageHeight - (tableHeadHeight * headNum) - tableTitle - 41;// 一页能放下的table的高度  41 是距离底部的边距 
+                    const tableRowNum = Math.floor(pageTableHeight / 40);//一页可放多少行
+                    // 按步长遍历,每次截取 表格行数 元素
+                    for (let j = 0; j < tableData.length; j += tableRowNum) {
+                        const group = tableData.slice(j, j + tableRowNum);
+                        const key = j/tableRowNum;
+                        //表格数据
+                        itemTableList.push(group);
+                        itemHeadder.push(groupDynamicHeader[i])
+                        // this.modulePageData.push(lastPageNum + key + 1);
+                        //页码
+                        itemTablePagesNum.push(lastPageNum + key + 1)
+                        //高度
+                        const tableHeight = group.length > 0 ? (tableHeadHeight * headNum) + tableTitle + tableGap + group.length * 40 : 0;
+                        moduleHeightData.push(tableHeight)
+                    }
+                }
+                this.modulePageData.push(...itemTablePagesNum);
+                this.moduleHeightData.push(...moduleHeightData);
+                // console.log(this.modulePageData,111333)
+                tableList.push(itemTableList)
+                headerList.push(itemHeadder)
+                tablePagesNum.push(itemTablePagesNum);//当前模块表格的分页页码
+            }
+            return {headerList,tablePagesNum,tableList}
+        },
+        /*
+        *单个图分页
+        *chartHeight 图表高度
+        */
+        SingleChartPage(chartHeight){
+            let chartPagesNum = '';//各分析图 高度
+            const prevModuleLastPageNum = this.modulePageData.length > 0 ? this.modulePageData[this.modulePageData.length - 1] : 1;//上一模块 最后一页页码
+            //上一模块最后一页高度 和 当前模块 echart高度
+            let prevModuleLastPageHeight = [],moduleChartHeight = [];
+            this.modulePageData.forEach((item,i)=>{
+                if(item == prevModuleLastPageNum){
+                    prevModuleLastPageHeight.push(this.moduleHeightData[i])
+                    moduleChartHeight.push(this.moduleHeightData[i]);//最后一页高度
+                }
+            })
+            // 将当前模块 echart高度
+            this.moduleHeightData.push(chartHeight)
+            moduleChartHeight.push(chartHeight)
+            //进行分页 满足一页的高度放在一起
+            const printPageGroup = this.groupByThreshold([...moduleChartHeight], this.printPageHeight);
+            //每个模块对应的页码
+            for (let i = 0; i < printPageGroup.length; i++) {
+                const arr = printPageGroup[i]
+                for (let j = 0; j < arr.length; j++) {
+                    if(i == 0){//第一页
+                        if(j > prevModuleLastPageHeight.length - 1){
+                            this.modulePageData.push(prevModuleLastPageNum)
+                            chartPagesNum = prevModuleLastPageNum
+                        }
+                    }else{
+                        this.modulePageData.push(prevModuleLastPageNum + i);
+                        chartPagesNum = prevModuleLastPageNum + i
+                    }
+                }
+            }
+            return chartPagesNum
+        },
+        GetDifficultyClass(val) {
+            if (val == 1) {
+                return 'difficulty easy'
+            } else if (val == 2) {
+                return 'difficulty relatively_easy'
+            } else if (val == 3) {
+                return 'difficulty general'
+            } else if (val == 4) {
+                return 'difficulty more_difficult'
+            } else if (val == 5) {
+                return 'difficulty difficult'
+            } else {
+                return ''
+            }
+        },
+        GetDifficultyName(val) {
+            if (val == 1) {
+                return '容易'
+            } else if (val == 2) {
+                return '较易'
+            } else if (val == 3) {
+                return '一般'
+            } else if (val == 4) {
+                return '较难'
+            } else if (val == 5) {
+                return '困难'
+            } else {
+                return '-'
+            }
+        },
+        //点击顶部下载PDF按钮导出
+        DownloadPdf(){
+            if(this.$refs.bookFlipBox){
+                this.$refs.bookFlipBox.DownloadPdfNew();
+            }
+        },
+        //向父级页面传值关闭下载Pdf按钮loading
+        PdfLoadEnd(){
+            this.$emit('closePdfLoading')
+        },
+    }
+};
+</script>
+<style scoped lang="scss">
+    .area_module_describe{
+        margin-top: 20px;
+    }
+    .card-container {
+        display: flex;
+        flex-wrap: wrap;
+        /* 允许换行 */
+        /* 设置元素之间间隔 */
+        gap: 10px;
+        .card {
+            //   flex: 0 1 calc((100% - 240px) / 5);
+            flex-grow: 1; /* 使每个div平分可用空间 */
+            flex-basis: 0; /* 初始基础大小为0 */
+            width: 20%;
+            //   max-width: 320px;
+            // width: 238px;
+            /* 每个卡片占据 18% 宽度,确保能在一行显示五个,考虑间隔 */
+            // flex: 0 0 16.5%; /* 每个卡片占据 18% 宽度,确保能在一行显示五个,考虑间隔 */
+            // border: 2px dashed #ccc;
+            border-radius: 10px;
+            padding: 12px 6px;
+            box-sizing: border-box;
+            text-align: center;
+            color: #fff;
+            /* 文字颜色可以根据背景调整 */
+            background-size: cover;
+            background-position: center;
+            height: 70px;
+            /* 可调整高度 */
+            // margin-bottom: 20px; /* 卡片底部间距 */
+            position: relative;
+        }
+        .background-image {
+            position: absolute;
+            top: 0;
+            right: 0;
+            /* 背景图片宽度 */
+            width: 100%;
+            height: 70px;
+            background-size: contain;
+            background-repeat: no-repeat;
+            background-position: center right;
+            z-index: 1;
+            /* 确保背景图片在文本之下 */
+            border-radius: 10px;
+        }
+        .statistic,.value {
+            position: relative;
+            z-index: 2;
+            /* 确保文本在背景图片之上 */
+        }
+        .statistic {
+            font-size: 13px;
+            font-weight: 400;
+            line-height: 22px;
+            text-align: left;
+            z-index: 2;
+        }
+        .value {
+            font-size: 16px;
+            font-weight: 600;
+            line-height: 20px;
+            text-align: left;
+            margin-top: 5px;
+            z-index: 2;
+        }
+    }
+    .area_module{
+        .area_module_content{
+            display: flex;
+            width: 100%;
+            justify-content: space-between;
+            .area_module_chart{
+                width: 35% !important;
+            }
+            .area_module_table{
+                width: 65% !important;
+            }
+        }
+        .area_module_chart{
+            &.justify_content{
+                display: flex;
+                gap: 0px 48px;
+                flex-wrap: wrap;
+                .item_progress{
+                    width: 170px;
+                    height: 180px;
+                    display: inline-flex;
+                    align-items: center;
+                    position: relative;
+                    :deep(.el-progress--circle) {
+                        .el-progress__text{
+                            font-size: 14px !important;
+                            color: #5470C6 !important;
+                            background: rgba(84,112,198,0.1);
+                            width: 120px;
+                            height: 120px;
+                            border-radius: 50%;
+                            position: absolute;
+                            left: 50%;
+                            top: 50%;
+                            transform: translate(-50%, -50%);
+                            display: flex;
+                            align-items: center;
+                            justify-content: center;
+                            padding-top: 30px;
+                            box-sizing: border-box;
+                        }
+                    }
+                    .item_progress_name{
+                        position: absolute;
+                        left: 50%;
+                        top: 37%;
+                        transform: translate(-50%,0);
+                        font-weight: 500;
+                        font-size: 16px !important;
+                        color: #5470C6 !important;
+                    }
+                }
+            }
+        }
+        .area_module_table{
+            &.error{
+                :deep() .el-table{
+                    .el-table__header-wrapper{
+                        .el-table__header{
+                            .is-group{
+                                tr{
+                                    th.el-table__cell:nth-last-child(3){
+                                        border-right: 1px solid #EBEEF5;
+                                    }
+                                    &:nth-child(2){
+                                        display:none;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    .el-table__cell{
+                        &.white_space_normal{
+                            .cell{
+                                white-space: normal;
+                                padding:10px 0 10px 10px !important;
+                            }
+                        }
+                    }
+                    // &.el-table--striped {
+                    //     .el-table__body {
+                    //         tr{
+                    //             &.el-table__row--striped{
+                    //                 &.row_color_FFFFFF {
+                    //                     td.el-table__cell{
+                    //                         background: #FFFFFF;
+                    //                     }
+                    //                 }
+                    //             }
+                    //         }
+                    //     }
+                    // }
+                    // &.el-table--striped .el-table__body tr.el-table__row.row_color_FAFAFA td.el-table__cell{
+                    //     background: #FAFAFA;
+                    // }
+                    // &.el-table--striped .el-table__body tr.el-table__row--striped.row_color_FAFAFA td.el-table__cell{
+                    //     background: #FAFAFA;
+                    // }
+                    // &.el-table--enable-row-hover .el-table__body tr.row_color_FFFFFF:hover > td {
+                    //     background-color: transparent !important;
+                    // }
+                }
+            }
+        }
+        .difficulty {
+            width: 6px;
+            height: 6px;
+            display: inline-flex;
+            border-radius: 50%;
+            margin-right: 4px;
+            &.easy {
+                background: #3ba272;
+            }
+            &.relatively_easy {
+                background: #fac858;
+            }
+            &.general {
+                background: #5470c6;
+            }
+            &.more_difficult {
+                background: #ea7acb;
+            }
+            &.difficult {
+                background: #ee6666;
+            }
+        }
+    }
+</style>

+ 11 - 2
src/views/analysisReport/studentPage/mainPage.vue

@@ -21,6 +21,7 @@
                     <el-button size="medium" @click="downloadWrongQuestions(0)">下载错题本</el-button>
                     <el-button type="primary" size="medium" @click="downloadWrongQuestions(1)">下载个性化提升手册</el-button>
                 </template>
+                <el-button style="margin-left: 10px;" size="medium" type="primary" :loading="stuPdfLoading" @click="StuDownloadPDF">下载PDF</el-button>
             </div>
         </div>
 
@@ -40,7 +41,7 @@
                                 <div class="page_filter" ref="filterContent">
                                     <FiltersItem :filtersData="filterData" @selectItem="ChangeFilters"></FiltersItem>
                                 </div>
-                                <router-view ref="child"></router-view>
+                                <router-view ref="child" @closePdfLoading="closePdfLoading"></router-view>
                             </div>
                         </div>
                     </template>
@@ -49,7 +50,7 @@
                         <div class="page_filter" ref="filterContent">
                             <FiltersItem :filtersData="filterData" @selectItem="ChangeFilters"></FiltersItem>
                         </div>
-                        <router-view ref="child"></router-view>
+                        <router-view ref="child" @closePdfLoading="closePdfLoading"></router-view>
                     </template>
                 </div>
             </div>
@@ -90,6 +91,7 @@ export default {
             pathOne: '/studentAnalysisReport/reportDetails/scrolReport',
             pathTwo: '/studentAnalysisReport/reportDetails/personalWrongQuestions',
             isLianXiao: false,//是否联校
+            stuPdfLoading:false,
         };
     },
 
@@ -213,6 +215,13 @@ export default {
             }    
             
         },
+        StuDownloadPDF() {
+            this.stuPdfLoading = true;
+            this.$refs.child.DownloadPdf();
+        },
+        closePdfLoading(){
+            this.stuPdfLoading = false;
+        },
     },
     watch: {
         '$route'(to, from) {

+ 15 - 4
src/views/analysisReport/studentPage/scrolReport/transcript.vue

@@ -3,6 +3,7 @@
   <div>
     <TranscriptTotal v-if="isTotalScore" />
     <TranscriptSingle v-else />
+    <StudentReport ref="studentReportPdf" @closePdfLoading="closePdfLoading"></StudentReport>
   </div>
 </template>
 
@@ -10,12 +11,13 @@
 
 import TranscriptSingle from "@/views/analysisReport/studentPage/scrolReport/transcript_single";//单科的G组分析
 import TranscriptTotal from "@/views/analysisReport/studentPage/scrolReport/transcript_total";//单科的G组分析
-
+import StudentReport from '../downloadPdf/studentReport.vue'
 
 export default {
   components: {
     TranscriptSingle,
-    TranscriptTotal
+    TranscriptTotal,
+    StudentReport
   },
   computed: {
     isTotalScore(){
@@ -32,8 +34,17 @@ export default {
   },
   mounted() {
   },
-  methods:
-  {
+  methods:{
+    //点击顶部下载PDF按钮导出
+    DownloadPdf(){
+        if(this.$refs.studentReportPdf){
+            this.$refs.studentReportPdf.DownloadPdf();
+        }
+    },
+    //向父级页面传值关闭下载Pdf按钮loading
+    closePdfLoading(){
+        this.$emit('closePdfLoading')
+    },
   },
 };
 </script>

+ 1 - 1
src/views/analysisReport/studentPage/scrolReport/transcript_single.vue

@@ -66,7 +66,7 @@
               <span class="title">年排</span>
               <span class="value">{{ subjectData?.data?.schoolRank ?? '-' }}</span>
             </div>
-            <div class="score_item" v-if="subjectData?.data?.examRankStatus === 0 && schoolType == 2">
+            <div class="score_item" v-if="subjectData?.data?.examRankStatus === 0 && reportParam.examLevel == 1 && schoolType == 2">
               <span class="title">联排</span>
               <span class="value">{{ subjectData?.data?.examRank ?? '-' }}</span>
             </div>

+ 7 - 0
vue.config.js

@@ -1,12 +1,19 @@
 
 const DonePlugin = require("./src/plugin/webpackDonePlugin.js");
 const WebpackBar = require("webpackbar");
+const webpack = require('webpack');
 const TerserPlugin = require("terser-webpack-plugin");
 console.log('当前环境:', process.env.NODE_ENV);
 module.exports = {
   transpileDependencies: true,
   lintOnSave: false,
   chainWebpack: (config) => {
+    config.plugin("provide").use(webpack.ProvidePlugin, [{
+        $: "jquery",
+        jquery: "jquery",
+        jQuery: "jquery",
+        "window.jQuery": "jquery",
+    },]);
     config.module
       .rule("lottie")
       .test(/\.lottie$/)