mo 1 year ago
parent
commit
00354c6675
67 changed files with 3505 additions and 139 deletions
  1. 1 0
      index.html
  2. 691 8
      package-lock.json
  3. 3 0
      package.json
  4. 3 2
      postcss.config.js
  5. 262 0
      src/custom-plugins/guide-page/attent-guide.tsx
  6. 229 0
      src/custom-plugins/guide-page/class-guide.tsx
  7. 236 0
      src/custom-plugins/guide-page/data-guide.tsx
  8. 58 28
      src/custom-plugins/guide-page/home-guide.tsx
  9. BIN
      src/custom-plugins/guide-page/images/attent1.png
  10. BIN
      src/custom-plugins/guide-page/images/attent2.png
  11. BIN
      src/custom-plugins/guide-page/images/attent3.png
  12. BIN
      src/custom-plugins/guide-page/images/attent4.png
  13. BIN
      src/custom-plugins/guide-page/images/class1.png
  14. BIN
      src/custom-plugins/guide-page/images/class2.png
  15. BIN
      src/custom-plugins/guide-page/images/class3.png
  16. BIN
      src/custom-plugins/guide-page/images/data1.png
  17. BIN
      src/custom-plugins/guide-page/images/data2.png
  18. BIN
      src/custom-plugins/guide-page/images/data3.png
  19. BIN
      src/custom-plugins/guide-page/images/endBtn.png
  20. BIN
      src/custom-plugins/guide-page/images/lessons1.png
  21. BIN
      src/custom-plugins/guide-page/images/lessons2.png
  22. BIN
      src/custom-plugins/guide-page/images/lessons3.png
  23. BIN
      src/custom-plugins/guide-page/images/lessons4.png
  24. BIN
      src/custom-plugins/guide-page/images/lessons5.png
  25. BIN
      src/custom-plugins/guide-page/images/music1.png
  26. BIN
      src/custom-plugins/guide-page/images/music2.png
  27. BIN
      src/custom-plugins/guide-page/images/music3.png
  28. BIN
      src/custom-plugins/guide-page/images/myColloge1.png
  29. BIN
      src/custom-plugins/guide-page/images/myResourecs1.png
  30. BIN
      src/custom-plugins/guide-page/images/shareResources1.png
  31. BIN
      src/custom-plugins/guide-page/images/shareResources2.png
  32. BIN
      src/custom-plugins/guide-page/images/student1.png
  33. BIN
      src/custom-plugins/guide-page/images/student2.png
  34. BIN
      src/custom-plugins/guide-page/images/teacher1.png
  35. BIN
      src/custom-plugins/guide-page/images/teacher2.png
  36. BIN
      src/custom-plugins/guide-page/images/train1.png
  37. BIN
      src/custom-plugins/guide-page/images/train2.png
  38. 39 8
      src/custom-plugins/guide-page/index.module.less
  39. 279 0
      src/custom-plugins/guide-page/lessons-guide.tsx
  40. 229 0
      src/custom-plugins/guide-page/music-guide.tsx
  41. 187 0
      src/custom-plugins/guide-page/myColloge-guide.tsx
  42. 190 0
      src/custom-plugins/guide-page/myResources-guide.tsx
  43. 212 0
      src/custom-plugins/guide-page/shareResources-guide.tsx
  44. 207 0
      src/custom-plugins/guide-page/student-guide.tsx
  45. 211 0
      src/custom-plugins/guide-page/teacher-guide.tsx
  46. 186 0
      src/custom-plugins/guide-page/train-guide.tsx
  47. 1 3
      src/main.ts
  48. 3 0
      src/utils/index.ts
  49. 18 0
      src/utils/rem.ts
  50. 10 1
      src/views/attend-class/index.tsx
  51. 82 35
      src/views/classList/index.tsx
  52. 14 4
      src/views/data-module/index.tsx
  53. 1 1
      src/views/home/index.tsx
  54. 8 4
      src/views/natural-resources/components/my-collect/index.tsx
  55. 7 2
      src/views/natural-resources/components/my-resources/index.tsx
  56. 1 0
      src/views/natural-resources/components/my-resources/search-group-resources.tsx
  57. 9 3
      src/views/natural-resources/components/share-resources/index.tsx
  58. 5 3
      src/views/natural-resources/index.tsx
  59. 1 0
      src/views/prepare-lessons/components/directory-main/index.tsx
  60. 2 1
      src/views/prepare-lessons/components/lesson-main/courseware/index.tsx
  61. 3 3
      src/views/prepare-lessons/components/lesson-main/index.tsx
  62. 10 3
      src/views/prepare-lessons/components/lesson-main/train/index.tsx
  63. 13 4
      src/views/prepare-lessons/index.tsx
  64. 8 3
      src/views/setting/components/schoolInfo/index.tsx
  65. 18 5
      src/views/studentList/index.tsx
  66. 60 18
      src/views/xiaoku-music/index.tsx
  67. 8 0
      vite.config.ts

+ 1 - 0
index.html

@@ -26,6 +26,7 @@
   <meta name="msapplication-tap-highlight" content="no" />
   <meta name="referrer" content="no-referrer" />
   <title>老师端</title>
+
   <style>
     body {
       background: #f1f5ff;

+ 691 - 8
package-lock.json

@@ -18,12 +18,15 @@
         "dayjs": "^1.11.7",
         "echarts": "^5.4.2",
         "html2canvas": "^1.4.1",
+        "lib-flexible": "^0.3.2",
         "lodash": "^4.17.21",
         "lodash-es": "^4.17.21",
         "moveable": "^0.49.0",
         "numeral": "^2.0.6",
         "pinia": "^2.1.4",
         "plyr": "^3.7.8",
+        "postcss-px2rem": "^0.3.0",
+        "px2rem-loader": "^0.1.9",
         "query-string": "^8.1.0",
         "terser": "^5.18.2",
         "umi-request": "^1.4.0",
@@ -3640,6 +3643,17 @@
         "node": ">= 4.0.0"
       }
     },
+    "node_modules/atob": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/atob/-/atob-2.1.2.tgz",
+      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+      "bin": {
+        "atob": "bin/atob.js"
+      },
+      "engines": {
+        "node": ">= 4.5.0"
+      }
+    },
     "node_modules/autoprefixer": {
       "version": "10.4.14",
       "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.14.tgz",
@@ -3752,6 +3766,14 @@
       "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
       "dev": true
     },
+    "node_modules/big.js": {
+      "version": "5.2.2",
+      "resolved": "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz",
+      "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/binary-extensions": {
       "version": "2.2.0",
       "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -4211,6 +4233,17 @@
         "node": ">=8"
       }
     },
+    "node_modules/css": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmmirror.com/css/-/css-2.2.4.tgz",
+      "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==",
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "source-map": "^0.6.1",
+        "source-map-resolve": "^0.5.2",
+        "urix": "^0.1.0"
+      }
+    },
     "node_modules/css-line-break": {
       "version": "2.1.0",
       "resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
@@ -4488,6 +4521,14 @@
       "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
       "dev": true
     },
+    "node_modules/emojis-list": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/emojis-list/-/emojis-list-3.0.0.tgz",
+      "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+      "engines": {
+        "node": ">= 4"
+      }
+    },
     "node_modules/encoding": {
       "version": "0.1.13",
       "resolved": "https://registry.npmmirror.com/encoding/-/encoding-0.1.13.tgz",
@@ -5034,8 +5075,7 @@
     "node_modules/extend": {
       "version": "3.0.2",
       "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz",
-      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
-      "dev": true
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
     },
     "node_modules/external-editor": {
       "version": "3.1.0",
@@ -5605,6 +5645,25 @@
         "node": ">= 0.4.0"
       }
     },
+    "node_modules/has-ansi": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/has-ansi/-/has-ansi-2.0.0.tgz",
+      "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
+      "dependencies": {
+        "ansi-regex": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/has-ansi/node_modules/ansi-regex": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-2.1.1.tgz",
+      "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/has-bigints": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.0.2.tgz",
@@ -6512,6 +6571,11 @@
         "node": ">=8"
       }
     },
+    "node_modules/js-base64": {
+      "version": "2.6.4",
+      "resolved": "https://registry.npmmirror.com/js-base64/-/js-base64-2.6.4.tgz",
+      "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ=="
+    },
     "node_modules/js-binary-schema-parser": {
       "version": "2.0.3",
       "resolved": "https://registry.npmmirror.com/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz",
@@ -6664,6 +6728,11 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/lib-flexible": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmmirror.com/lib-flexible/-/lib-flexible-0.3.2.tgz",
+      "integrity": "sha512-9yowMWA70tKhKdCJDaltY0mNQG4OWo7pWKScnTp9aiSxS7s20ZYlwBRE3335nweOf5qKXVC7sDxJwMPM8/MFZg=="
+    },
     "node_modules/liftoff": {
       "version": "4.0.0",
       "resolved": "https://registry.npmmirror.com/liftoff/-/liftoff-4.0.0.tgz",
@@ -6841,6 +6910,30 @@
         "node": ">=8"
       }
     },
+    "node_modules/loader-utils": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz",
+      "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
+      "dependencies": {
+        "big.js": "^5.2.2",
+        "emojis-list": "^3.0.0",
+        "json5": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/loader-utils/node_modules/json5": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz",
+      "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+      "dependencies": {
+        "minimist": "^1.2.0"
+      },
+      "bin": {
+        "json5": "lib/cli.js"
+      }
+    },
     "node_modules/loadjs": {
       "version": "4.2.0",
       "resolved": "https://registry.npmmirror.com/loadjs/-/loadjs-4.2.0.tgz",
@@ -7206,8 +7299,7 @@
     "node_modules/minimist": {
       "version": "1.2.8",
       "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz",
-      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
-      "dev": true
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
     },
     "node_modules/mkdirp": {
       "version": "1.0.4",
@@ -7290,6 +7382,12 @@
         "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
       }
     },
+    "node_modules/natives": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmmirror.com/natives/-/natives-1.1.6.tgz",
+      "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==",
+      "deprecated": "This module relies on Node.js's internals and will break at some point. Do not use it, and update to graceful-fs@4.x."
+    },
     "node_modules/natural-compare": {
       "version": "1.4.0",
       "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -8007,6 +8105,106 @@
         "postcss": ">=5.0.2"
       }
     },
+    "node_modules/postcss-px2rem": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmmirror.com/postcss-px2rem/-/postcss-px2rem-0.3.0.tgz",
+      "integrity": "sha512-ACZRimmOEDma0L/sI5ENREY3BoYB4LNME9iM9VcZU2t598OB9KLEPDYX8JBohNsvwJ+Nvlvk3IcGm0bRqOBC/Q==",
+      "dependencies": {
+        "postcss": "^5.0.0",
+        "px2rem": "~0.5.0"
+      }
+    },
+    "node_modules/postcss-px2rem/node_modules/ansi-regex": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-2.1.1.tgz",
+      "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postcss-px2rem/node_modules/ansi-styles": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-2.2.1.tgz",
+      "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postcss-px2rem/node_modules/chalk": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmmirror.com/chalk/-/chalk-1.1.3.tgz",
+      "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
+      "dependencies": {
+        "ansi-styles": "^2.2.1",
+        "escape-string-regexp": "^1.0.2",
+        "has-ansi": "^2.0.0",
+        "strip-ansi": "^3.0.0",
+        "supports-color": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postcss-px2rem/node_modules/chalk/node_modules/supports-color": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-2.0.0.tgz",
+      "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/postcss-px2rem/node_modules/has-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-1.0.0.tgz",
+      "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postcss-px2rem/node_modules/postcss": {
+      "version": "5.2.18",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-5.2.18.tgz",
+      "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==",
+      "dependencies": {
+        "chalk": "^1.1.3",
+        "js-base64": "^2.1.9",
+        "source-map": "^0.5.6",
+        "supports-color": "^3.2.3"
+      },
+      "engines": {
+        "node": ">=0.12"
+      }
+    },
+    "node_modules/postcss-px2rem/node_modules/source-map": {
+      "version": "0.5.7",
+      "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz",
+      "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postcss-px2rem/node_modules/strip-ansi": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-3.0.1.tgz",
+      "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
+      "dependencies": {
+        "ansi-regex": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postcss-px2rem/node_modules/supports-color": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-3.2.3.tgz",
+      "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==",
+      "dependencies": {
+        "has-flag": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
     "node_modules/postcss-selector-parser": {
       "version": "6.0.13",
       "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
@@ -8094,6 +8292,154 @@
         "node": ">=6"
       }
     },
+    "node_modules/px2rem": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmmirror.com/px2rem/-/px2rem-0.5.0.tgz",
+      "integrity": "sha512-R+LQj3Evbjbpmglo7D0PBVsnAbKP4WSvZEZUnF8RGIpWkIHFeAT+BlDOxxBxKVyMDecmfv9qdzNLTZLMq32osA==",
+      "dependencies": {
+        "chalk": "~0.5.1",
+        "commander": "~2.6.0",
+        "css": "~2.2.0",
+        "extend": "~3.0.0",
+        "fs-extra": "~0.16.3"
+      },
+      "bin": {
+        "px2rem": "bin/px2rem.js"
+      }
+    },
+    "node_modules/px2rem-loader": {
+      "version": "0.1.9",
+      "resolved": "https://registry.npmmirror.com/px2rem-loader/-/px2rem-loader-0.1.9.tgz",
+      "integrity": "sha512-3Ew8At5W/HHIIUe/KZk+FBRRb20KtgP1N1c/BnMlXk6LNkqrFmUIUF35GF/evzNdj/Q63iWJpkmn/c5qSMplRg==",
+      "dependencies": {
+        "loader-utils": "^1.1.0",
+        "px2rem": "^0.5.0"
+      }
+    },
+    "node_modules/px2rem/node_modules/ansi-regex": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-0.2.1.tgz",
+      "integrity": "sha512-sGwIGMjhYdW26/IhwK2gkWWI8DRCVO6uj3hYgHT+zD+QL1pa37tM3ujhyfcJIYSbsxp7Gxhy7zrRW/1AHm4BmA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/px2rem/node_modules/ansi-styles": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-1.1.0.tgz",
+      "integrity": "sha512-f2PKUkN5QngiSemowa6Mrk9MPCdtFiOSmibjZ+j1qhLGHHYsqZwmBMRF3IRMVXo8sybDqx2fJl2d/8OphBoWkA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/px2rem/node_modules/chalk": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmmirror.com/chalk/-/chalk-0.5.1.tgz",
+      "integrity": "sha512-bIKA54hP8iZhyDT81TOsJiQvR1gW+ZYSXFaZUAvoD4wCHdbHY2actmpTE4x344ZlFqHbvoxKOaESULTZN2gstg==",
+      "dependencies": {
+        "ansi-styles": "^1.1.0",
+        "escape-string-regexp": "^1.0.0",
+        "has-ansi": "^0.1.0",
+        "strip-ansi": "^0.3.0",
+        "supports-color": "^0.2.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/px2rem/node_modules/commander": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmmirror.com/commander/-/commander-2.6.0.tgz",
+      "integrity": "sha512-PhbTMT+ilDXZKqH8xbvuUY2ZEQNef0Q7DKxgoEKb4ccytsdvVVJmYqR0sGbi96nxU6oGrwEIQnclpK2NBZuQlg==",
+      "engines": {
+        "node": ">= 0.6.x"
+      }
+    },
+    "node_modules/px2rem/node_modules/fs-extra": {
+      "version": "0.16.5",
+      "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-0.16.5.tgz",
+      "integrity": "sha512-yb7ti8kVH+qboUQWYxUuOPj/qcMUA6lO68ErZoPQOTP+7qroCIN/1gZ1lLk/rs2p0gPFzrvPYujKGnHTu+HHxA==",
+      "dependencies": {
+        "graceful-fs": "^3.0.5",
+        "jsonfile": "^2.0.0",
+        "rimraf": "^2.2.8"
+      }
+    },
+    "node_modules/px2rem/node_modules/graceful-fs": {
+      "version": "3.0.12",
+      "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-3.0.12.tgz",
+      "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==",
+      "dependencies": {
+        "natives": "^1.1.3"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/px2rem/node_modules/has-ansi": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/has-ansi/-/has-ansi-0.1.0.tgz",
+      "integrity": "sha512-1YsTg1fk2/6JToQhtZkArMkurq8UoWU1Qe0aR3VUHjgij4nOylSWLWAtBXoZ4/dXOmugfLGm1c+QhuD0JyedFA==",
+      "dependencies": {
+        "ansi-regex": "^0.2.0"
+      },
+      "bin": {
+        "has-ansi": "cli.js"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/px2rem/node_modules/jsonfile": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-2.4.0.tgz",
+      "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==",
+      "optionalDependencies": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
+    "node_modules/px2rem/node_modules/jsonfile/node_modules/graceful-fs": {
+      "version": "4.2.11",
+      "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+      "optional": true
+    },
+    "node_modules/px2rem/node_modules/rimraf": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz",
+      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+      "dependencies": {
+        "glob": "^7.1.3"
+      },
+      "bin": {
+        "rimraf": "bin.js"
+      }
+    },
+    "node_modules/px2rem/node_modules/strip-ansi": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-0.3.0.tgz",
+      "integrity": "sha512-DerhZL7j6i6/nEnVG0qViKXI0OKouvvpsAiaj7c+LfqZZZxdwZtv8+UiA/w4VUJpT8UzX0pR1dcHOii1GbmruQ==",
+      "dependencies": {
+        "ansi-regex": "^0.2.1"
+      },
+      "bin": {
+        "strip-ansi": "cli.js"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/px2rem/node_modules/supports-color": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-0.2.0.tgz",
+      "integrity": "sha512-tdCZ28MnM7k7cJDJc7Eq80A9CsRFAAOZUy41npOZCs++qSjfIy7o5Rh46CBk+Dk5FbKJ33X3Tqg4YrV07N5RaA==",
+      "bin": {
+        "supports-color": "cli.js"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/qs": {
       "version": "6.11.2",
       "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.2.tgz",
@@ -8331,6 +8677,12 @@
         "node": ">=4"
       }
     },
+    "node_modules/resolve-url": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmmirror.com/resolve-url/-/resolve-url-0.2.1.tgz",
+      "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==",
+      "deprecated": "https://github.com/lydell/resolve-url#deprecated"
+    },
     "node_modules/restore-cursor": {
       "version": "3.1.0",
       "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-3.1.0.tgz",
@@ -8654,6 +9006,27 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/source-map-resolve": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmmirror.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
+      "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
+      "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated",
+      "dependencies": {
+        "atob": "^2.1.2",
+        "decode-uri-component": "^0.2.0",
+        "resolve-url": "^0.2.1",
+        "source-map-url": "^0.4.0",
+        "urix": "^0.1.0"
+      }
+    },
+    "node_modules/source-map-resolve/node_modules/decode-uri-component": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
+      "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
     "node_modules/source-map-support": {
       "version": "0.5.21",
       "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -8663,6 +9036,12 @@
         "source-map": "^0.6.0"
       }
     },
+    "node_modules/source-map-url": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/source-map-url/-/source-map-url-0.4.1.tgz",
+      "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
+      "deprecated": "See https://github.com/lydell/source-map-url#deprecated"
+    },
     "node_modules/sourcemap-codec": {
       "version": "1.4.8",
       "resolved": "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
@@ -9362,6 +9741,12 @@
         "punycode": "^2.1.0"
       }
     },
+    "node_modules/urix": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/urix/-/urix-0.1.0.tgz",
+      "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==",
+      "deprecated": "Please see https://github.com/lydell/urix#deprecated"
+    },
     "node_modules/url-polyfill": {
       "version": "1.1.12",
       "resolved": "https://registry.npmmirror.com/url-polyfill/-/url-polyfill-1.1.12.tgz",
@@ -12940,6 +13325,11 @@
       "resolved": "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz",
       "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
     },
+    "atob": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/atob/-/atob-2.1.2.tgz",
+      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
+    },
     "autoprefixer": {
       "version": "10.4.14",
       "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.14.tgz",
@@ -13028,6 +13418,11 @@
       "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
       "dev": true
     },
+    "big.js": {
+      "version": "5.2.2",
+      "resolved": "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz",
+      "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
+    },
     "binary-extensions": {
       "version": "2.2.0",
       "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -13417,6 +13812,17 @@
       "resolved": "https://registry.npmmirror.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
       "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="
     },
+    "css": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmmirror.com/css/-/css-2.2.4.tgz",
+      "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==",
+      "requires": {
+        "inherits": "^2.0.3",
+        "source-map": "^0.6.1",
+        "source-map-resolve": "^0.5.2",
+        "urix": "^0.1.0"
+      }
+    },
     "css-line-break": {
       "version": "2.1.0",
       "resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
@@ -13641,6 +14047,11 @@
       "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
       "dev": true
     },
+    "emojis-list": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/emojis-list/-/emojis-list-3.0.0.tgz",
+      "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="
+    },
     "encoding": {
       "version": "0.1.13",
       "resolved": "https://registry.npmmirror.com/encoding/-/encoding-0.1.13.tgz",
@@ -14068,8 +14479,7 @@
     "extend": {
       "version": "3.0.2",
       "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz",
-      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
-      "dev": true
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
     },
     "external-editor": {
       "version": "3.1.0",
@@ -14528,6 +14938,21 @@
         "function-bind": "^1.1.1"
       }
     },
+    "has-ansi": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/has-ansi/-/has-ansi-2.0.0.tgz",
+      "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
+      "requires": {
+        "ansi-regex": "^2.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="
+        }
+      }
+    },
     "has-bigints": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.0.2.tgz",
@@ -15225,6 +15650,11 @@
         }
       }
     },
+    "js-base64": {
+      "version": "2.6.4",
+      "resolved": "https://registry.npmmirror.com/js-base64/-/js-base64-2.6.4.tgz",
+      "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ=="
+    },
     "js-binary-schema-parser": {
       "version": "2.0.3",
       "resolved": "https://registry.npmmirror.com/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz",
@@ -15340,6 +15770,11 @@
         "type-check": "~0.4.0"
       }
     },
+    "lib-flexible": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmmirror.com/lib-flexible/-/lib-flexible-0.3.2.tgz",
+      "integrity": "sha512-9yowMWA70tKhKdCJDaltY0mNQG4OWo7pWKScnTp9aiSxS7s20ZYlwBRE3335nweOf5qKXVC7sDxJwMPM8/MFZg=="
+    },
     "liftoff": {
       "version": "4.0.0",
       "resolved": "https://registry.npmmirror.com/liftoff/-/liftoff-4.0.0.tgz",
@@ -15477,6 +15912,26 @@
         }
       }
     },
+    "loader-utils": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz",
+      "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
+      "requires": {
+        "big.js": "^5.2.2",
+        "emojis-list": "^3.0.0",
+        "json5": "^1.0.1"
+      },
+      "dependencies": {
+        "json5": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz",
+          "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+          "requires": {
+            "minimist": "^1.2.0"
+          }
+        }
+      }
+    },
     "loadjs": {
       "version": "4.2.0",
       "resolved": "https://registry.npmmirror.com/loadjs/-/loadjs-4.2.0.tgz",
@@ -15773,8 +16228,7 @@
     "minimist": {
       "version": "1.2.8",
       "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz",
-      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
-      "dev": true
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
     },
     "mkdirp": {
       "version": "1.0.4",
@@ -15842,6 +16296,11 @@
       "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz",
       "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
     },
+    "natives": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmmirror.com/natives/-/natives-1.1.6.tgz",
+      "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA=="
+    },
     "natural-compare": {
       "version": "1.4.0",
       "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -16408,6 +16867,83 @@
         "postcss": ">=5.0.2"
       }
     },
+    "postcss-px2rem": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmmirror.com/postcss-px2rem/-/postcss-px2rem-0.3.0.tgz",
+      "integrity": "sha512-ACZRimmOEDma0L/sI5ENREY3BoYB4LNME9iM9VcZU2t598OB9KLEPDYX8JBohNsvwJ+Nvlvk3IcGm0bRqOBC/Q==",
+      "requires": {
+        "postcss": "^5.0.0",
+        "px2rem": "~0.5.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="
+        },
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA=="
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmmirror.com/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          },
+          "dependencies": {
+            "supports-color": {
+              "version": "2.0.0",
+              "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-2.0.0.tgz",
+              "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g=="
+            }
+          }
+        },
+        "has-flag": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-1.0.0.tgz",
+          "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA=="
+        },
+        "postcss": {
+          "version": "5.2.18",
+          "resolved": "https://registry.npmmirror.com/postcss/-/postcss-5.2.18.tgz",
+          "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==",
+          "requires": {
+            "chalk": "^1.1.3",
+            "js-base64": "^2.1.9",
+            "source-map": "^0.5.6",
+            "supports-color": "^3.2.3"
+          }
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
+          "requires": {
+            "ansi-regex": "^2.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "3.2.3",
+          "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-3.2.3.tgz",
+          "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==",
+          "requires": {
+            "has-flag": "^1.0.0"
+          }
+        }
+      }
+    },
     "postcss-selector-parser": {
       "version": "6.0.13",
       "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
@@ -16474,6 +17010,119 @@
       "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.0.tgz",
       "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA=="
     },
+    "px2rem": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmmirror.com/px2rem/-/px2rem-0.5.0.tgz",
+      "integrity": "sha512-R+LQj3Evbjbpmglo7D0PBVsnAbKP4WSvZEZUnF8RGIpWkIHFeAT+BlDOxxBxKVyMDecmfv9qdzNLTZLMq32osA==",
+      "requires": {
+        "chalk": "~0.5.1",
+        "commander": "~2.6.0",
+        "css": "~2.2.0",
+        "extend": "~3.0.0",
+        "fs-extra": "~0.16.3"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-0.2.1.tgz",
+          "integrity": "sha512-sGwIGMjhYdW26/IhwK2gkWWI8DRCVO6uj3hYgHT+zD+QL1pa37tM3ujhyfcJIYSbsxp7Gxhy7zrRW/1AHm4BmA=="
+        },
+        "ansi-styles": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-1.1.0.tgz",
+          "integrity": "sha512-f2PKUkN5QngiSemowa6Mrk9MPCdtFiOSmibjZ+j1qhLGHHYsqZwmBMRF3IRMVXo8sybDqx2fJl2d/8OphBoWkA=="
+        },
+        "chalk": {
+          "version": "0.5.1",
+          "resolved": "https://registry.npmmirror.com/chalk/-/chalk-0.5.1.tgz",
+          "integrity": "sha512-bIKA54hP8iZhyDT81TOsJiQvR1gW+ZYSXFaZUAvoD4wCHdbHY2actmpTE4x344ZlFqHbvoxKOaESULTZN2gstg==",
+          "requires": {
+            "ansi-styles": "^1.1.0",
+            "escape-string-regexp": "^1.0.0",
+            "has-ansi": "^0.1.0",
+            "strip-ansi": "^0.3.0",
+            "supports-color": "^0.2.0"
+          }
+        },
+        "commander": {
+          "version": "2.6.0",
+          "resolved": "https://registry.npmmirror.com/commander/-/commander-2.6.0.tgz",
+          "integrity": "sha512-PhbTMT+ilDXZKqH8xbvuUY2ZEQNef0Q7DKxgoEKb4ccytsdvVVJmYqR0sGbi96nxU6oGrwEIQnclpK2NBZuQlg=="
+        },
+        "fs-extra": {
+          "version": "0.16.5",
+          "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-0.16.5.tgz",
+          "integrity": "sha512-yb7ti8kVH+qboUQWYxUuOPj/qcMUA6lO68ErZoPQOTP+7qroCIN/1gZ1lLk/rs2p0gPFzrvPYujKGnHTu+HHxA==",
+          "requires": {
+            "graceful-fs": "^3.0.5",
+            "jsonfile": "^2.0.0",
+            "rimraf": "^2.2.8"
+          }
+        },
+        "graceful-fs": {
+          "version": "3.0.12",
+          "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-3.0.12.tgz",
+          "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==",
+          "requires": {
+            "natives": "^1.1.3"
+          }
+        },
+        "has-ansi": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmmirror.com/has-ansi/-/has-ansi-0.1.0.tgz",
+          "integrity": "sha512-1YsTg1fk2/6JToQhtZkArMkurq8UoWU1Qe0aR3VUHjgij4nOylSWLWAtBXoZ4/dXOmugfLGm1c+QhuD0JyedFA==",
+          "requires": {
+            "ansi-regex": "^0.2.0"
+          }
+        },
+        "jsonfile": {
+          "version": "2.4.0",
+          "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-2.4.0.tgz",
+          "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==",
+          "requires": {
+            "graceful-fs": "^4.1.6"
+          },
+          "dependencies": {
+            "graceful-fs": {
+              "version": "4.2.11",
+              "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
+              "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+              "optional": true
+            }
+          }
+        },
+        "rimraf": {
+          "version": "2.7.1",
+          "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz",
+          "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+          "requires": {
+            "glob": "^7.1.3"
+          }
+        },
+        "strip-ansi": {
+          "version": "0.3.0",
+          "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-0.3.0.tgz",
+          "integrity": "sha512-DerhZL7j6i6/nEnVG0qViKXI0OKouvvpsAiaj7c+LfqZZZxdwZtv8+UiA/w4VUJpT8UzX0pR1dcHOii1GbmruQ==",
+          "requires": {
+            "ansi-regex": "^0.2.1"
+          }
+        },
+        "supports-color": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-0.2.0.tgz",
+          "integrity": "sha512-tdCZ28MnM7k7cJDJc7Eq80A9CsRFAAOZUy41npOZCs++qSjfIy7o5Rh46CBk+Dk5FbKJ33X3Tqg4YrV07N5RaA=="
+        }
+      }
+    },
+    "px2rem-loader": {
+      "version": "0.1.9",
+      "resolved": "https://registry.npmmirror.com/px2rem-loader/-/px2rem-loader-0.1.9.tgz",
+      "integrity": "sha512-3Ew8At5W/HHIIUe/KZk+FBRRb20KtgP1N1c/BnMlXk6LNkqrFmUIUF35GF/evzNdj/Q63iWJpkmn/c5qSMplRg==",
+      "requires": {
+        "loader-utils": "^1.1.0",
+        "px2rem": "^0.5.0"
+      }
+    },
     "qs": {
       "version": "6.11.2",
       "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.2.tgz",
@@ -16671,6 +17320,11 @@
       "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
       "dev": true
     },
+    "resolve-url": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmmirror.com/resolve-url/-/resolve-url-0.2.1.tgz",
+      "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg=="
+    },
     "restore-cursor": {
       "version": "3.1.0",
       "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-3.1.0.tgz",
@@ -16941,6 +17595,25 @@
       "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz",
       "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
     },
+    "source-map-resolve": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmmirror.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
+      "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
+      "requires": {
+        "atob": "^2.1.2",
+        "decode-uri-component": "^0.2.0",
+        "resolve-url": "^0.2.1",
+        "source-map-url": "^0.4.0",
+        "urix": "^0.1.0"
+      },
+      "dependencies": {
+        "decode-uri-component": {
+          "version": "0.2.2",
+          "resolved": "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
+          "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
+        }
+      }
+    },
     "source-map-support": {
       "version": "0.5.21",
       "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -16950,6 +17623,11 @@
         "source-map": "^0.6.0"
       }
     },
+    "source-map-url": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/source-map-url/-/source-map-url-0.4.1.tgz",
+      "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw=="
+    },
     "sourcemap-codec": {
       "version": "1.4.8",
       "resolved": "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
@@ -17499,6 +18177,11 @@
         "punycode": "^2.1.0"
       }
     },
+    "urix": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/urix/-/urix-0.1.0.tgz",
+      "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg=="
+    },
     "url-polyfill": {
       "version": "1.1.12",
       "resolved": "https://registry.npmmirror.com/url-polyfill/-/url-polyfill-1.1.12.tgz",

+ 3 - 0
package.json

@@ -31,12 +31,15 @@
     "dayjs": "^1.11.7",
     "echarts": "^5.4.2",
     "html2canvas": "^1.4.1",
+    "lib-flexible": "^0.3.2",
     "lodash": "^4.17.21",
     "lodash-es": "^4.17.21",
     "moveable": "^0.49.0",
     "numeral": "^2.0.6",
     "pinia": "^2.1.4",
     "plyr": "^3.7.8",
+    "postcss-px2rem": "^0.3.0",
+    "px2rem-loader": "^0.1.9",
     "query-string": "^8.1.0",
     "terser": "^5.18.2",
     "umi-request": "^1.4.0",

+ 3 - 2
postcss.config.js

@@ -2,11 +2,12 @@ module.exports = {
   plugins: {
     'postcss-px-to-viewport': {
       unitToConvert: 'px', // 需要转换的单位,默认为"px"
-      viewportWidth: 1920, // 设计稿的视口宽度
+      viewportWidth: 1920,
+      viewportHeight: 1188, // 设计稿的视口宽度
       unitPrecision: 5, // 单位转换后保留的精度
       propList: ['*'], // 能转化为vw的属性列表
       viewportUnit: 'vw', // 希望使用的视口单位
-      fontViewportUnit: 'vw', // 字体使用的视口单位
+      fontViewportUnit: 'rem', // 字体使用的视口单位
       selectorBlackList: [], // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
       minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
       mediaQuery: false, // 媒体查询里的单位是否需要转换单位

+ 262 - 0
src/custom-plugins/guide-page/attent-guide.tsx

@@ -0,0 +1,262 @@
+import { NButton } from 'naive-ui';
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import { getImage } from './images';
+import { px2vw, px2vwH } from '@/utils/index';
+export default defineComponent({
+  name: 'attent-guide',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const data = reactive({
+      box: {
+        height: '0px'
+      } as any,
+      show: false,
+      /**
+       *
+            width:  px2vw(840),
+            height:  px2vw(295)
+       */
+      steps: [
+        {
+          ele: '',
+          eleRect: {} as DOMRect,
+          img: getImage('attent1.png'),
+          handStyle: {
+            top: '0.91rem'
+          },
+          imgStyle: {
+            width: '647px',
+            height: '223px',
+            left:'-647px',
+            top:'-150px'
+          },
+          btnsStyle: {
+            bottom: '180px',
+            left: '-490px'
+          },
+          eleRectPadding: {
+            left: 7,
+            top: 7,
+            width: 14,
+            height: 14
+          },
+          type: 'left'
+        },
+        {
+          ele: '',
+          img: getImage('attent2.png'),
+          imgStyle: {
+            top: '100%',
+            left: '-317px',
+            width: '477px',
+            height: '277px'
+          },
+          btnsStyle: {
+            bottom: '90px',
+            left: '-155px'
+          },
+          boxStyle:{
+            borderRadius: '50%'
+          },
+          eleRectPadding: {
+            left: 7,
+            top: 7,
+            width: 14,
+            height: 14
+          },type:'bottom'
+        },
+        {
+          ele: '',
+          img: getImage('attent3.png'),
+          imgStyle: {
+            top: '100%',
+            left: '-317px',
+            width: '382px',
+            height: '277px'
+          },
+          btnsStyle: {
+            bottom: '80px',
+            left: '-155px'
+          },
+          boxStyle:{
+            borderRadius: '50%'
+          },
+          eleRectPadding: {
+            left: 7,
+            top: 7,
+            width: 14,
+            height: 14
+          },type:'bottom'
+        },
+        {
+          ele: '',
+          img: getImage('attent4.png'),
+          imgStyle: {
+            top: '100%',
+            left: '-10px',
+            width: '515px',
+            height: '227px'
+          },
+          btnsStyle: {
+            bottom: '30px',
+            left: '-30px'
+          },
+
+          eleRectPadding: {
+            left: 7,
+            top: 7,
+            width: 14,
+            height: 14
+          }
+        }
+      ],
+      step: 0
+    });
+    const tipShow = ref(false);
+    const guideInfo = localStorage.getItem('teacher-guideInfo');
+    if (guideInfo && JSON.parse(guideInfo).attentGuide) {
+      tipShow.value = false;
+    } else {
+      tipShow.value = true;
+    }
+    const getStepELe = () => {
+      const ele: HTMLElement = document.getElementById(`attent-${data.step}`)!;
+
+      if (ele) {
+        const eleRect = ele.getBoundingClientRect();
+
+        const left = data.steps[data.step].eleRectPadding?.left || 0;
+        const top = data.steps[data.step].eleRectPadding?.top || 0;
+        const width = data.steps[data.step].eleRectPadding?.width || 0;
+        const height = data.steps[data.step].eleRectPadding?.height || 0;
+        data.box = {
+          left: eleRect.x - left + 'px',
+          top: eleRect.y - top + 'px',
+          width: eleRect.width + width + 'px',
+          height: eleRect.height + height + 'px'
+        };
+        console.log(`coai-${data.step}`, data.box);
+      }
+    };
+    onMounted(() => {
+      getStepELe();
+    });
+
+    const handleNext = () => {
+      if (data.step >= 4) {
+        endGuide();
+        return;
+      }
+      data.step = data.step + 1;
+      getStepELe();
+    };
+
+    const endGuide = () => {
+      let guideInfo =
+        JSON.parse(localStorage.getItem('teacher-guideInfo')) || null;
+      if (!guideInfo) {
+        guideInfo = { attentGuide: true };
+      } else {
+        guideInfo.attentGuide = true;
+      }
+      localStorage.setItem('teacher-guideInfo', JSON.stringify(guideInfo));
+      tipShow.value = false;
+      //  localStorage.setItem('endC')
+    };
+    return () => (
+      <>
+        {tipShow.value ? (
+          <div
+            v-model:show={tipShow.value}
+            class={['n-modal-mask', 'n-modal-mask-guide']}>
+            <div class={styles.content} onClick={() => handleNext()}>
+              <div
+                class={styles.backBtn}
+                onClick={(e: Event) => {
+                  e.stopPropagation();
+                  endGuide();
+                }}>
+                跳过
+              </div>
+              <div
+                class={styles.box}
+                style={{ ...data.box, ...data.steps[data.step].boxStyle }}
+                id={`modeType-${data.step}`}>
+                {data.steps.map((item: any, index) => (
+                  <div
+                    onClick={(e: Event) => e.stopPropagation()}
+                    class={styles.item}
+                    style={
+                      item.type == 'bottom'
+                        ? {
+                            display: index === data.step ? '' : 'none',
+                            left: `${item.eleRect?.left}px`,
+                            top: `-${item.imgStyle?.height}`
+                          }
+                        : item.type == 'left'
+                        ? {
+                            display: index === data.step ? '' : 'none',
+                            left: `${item.eleRect?.left}px`,
+                            top: `${
+                              parseFloat(data.box?.height) / 2 -
+                              parseFloat(item.imgStyle?.height) / 20 +
+                              14
+                            }px`
+                          }
+                        : {
+                            display: index === data.step ? '' : 'none',
+                            left: `${item.eleRect?.left}px`,
+                            top: `${data.box?.height}`
+                          }
+                    }>
+                    <img
+                      class={styles.img}
+                      style={item.imgStyle}
+                      src={item.img}
+                    />
+                    {/* <img
+                      class={styles.iconHead}
+                      style={item.handStyle}
+                      src={getImage('indexDot.png')}
+                    /> */}
+                    <div class={styles.btns} style={item.btnsStyle}>
+                      {data.step + 1 == data.steps.length ? (
+                        <>
+                          <div
+                            class={[styles.endBtn]}
+                            onClick={() => endGuide()}>
+                            完成
+                          </div>
+                          <div
+                            class={styles.nextBtn}
+                            onClick={() => {
+                              data.step = 0;
+                              getStepELe();
+                            }}>
+                            再看一遍
+                          </div>
+                        </>
+                      ) : (
+                        <div class={styles.btn} onClick={() => handleNext()}>
+                          下一步 ({data.step + 1}/{data.steps.length})
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        ) : null}
+      </>
+    );
+  }
+});

+ 229 - 0
src/custom-plugins/guide-page/class-guide.tsx

@@ -0,0 +1,229 @@
+import { NButton } from 'naive-ui';
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import { getImage } from './images';
+import {px2vw,px2vwH} from '@/utils/index'
+export default defineComponent({
+  name: 'coai-guide',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const data = reactive({
+      box: {
+        height:'0px',
+      } as any,
+      show: false,
+      /**
+       *
+            width:  px2vw(840),
+            height:  px2vw(295)
+       */
+      steps: [
+        {
+          ele: '',
+          eleRect: {} as DOMRect,
+          img: getImage('class1.png'),
+          handStyle: {
+            top: '0.91rem'
+          },
+          imgStyle: {
+            left: '-294px',
+            width:  '648px',
+            height:  '227px'
+          },
+          btnsStyle: {
+            bottom:'30px',
+            left: '-90px',
+          },
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          }
+        },
+        {
+          ele: '',
+          img: getImage('class2.png'),
+          imgStyle: {
+            top: '100%',
+            left:  '-283px',
+            width: '515px',
+            height:'227px'
+          },
+          btnsStyle: {
+            bottom: '30px',
+            left: px2vw(-90),
+          },
+          boxStyle:{
+            borderRadius:'25px'
+          },
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          }
+
+        },
+        {
+          ele: '',
+          img: getImage('class3.png'),
+          imgStyle: {
+            width:  '437px',
+            height:  '227px',
+            left:'-282px'
+          },
+          btnsStyle: {
+            bottom: '30px',
+            left: '-130px',
+          },
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          }
+        },
+
+      ],
+      step: 0
+    });
+    const tipShow = ref(false);
+    const guideInfo = localStorage.getItem('teacher-guideInfo');
+    if (guideInfo && JSON.parse(guideInfo).classGuide) {
+      tipShow.value = false;
+    } else {
+      tipShow.value = true;
+    }
+    const getStepELe = () => {
+
+      const ele: HTMLElement = document.getElementById(`class-${data.step}`)!;
+
+      if (ele) {
+        const eleRect = ele.getBoundingClientRect();
+
+        const left = data.steps[data.step].eleRectPadding?.left || 0;
+        const top = data.steps[data.step].eleRectPadding?.top || 0;
+        const width = data.steps[data.step].eleRectPadding?.width || 0;
+        const height = data.steps[data.step].eleRectPadding?.height || 0
+        data.box = {
+          left: eleRect.x - left+ 'px',
+          top: eleRect.y - top +'px',
+          width: eleRect.width + width+'px',
+          height: eleRect.height +height+ 'px'
+        };
+        console.log(`coai-${data.step}`,data.box);
+      }
+    };
+    onMounted(() => {
+      getStepELe();
+    });
+
+    const handleNext = () => {
+      if (data.step >= 4) {
+        endGuide();
+        return;
+      }
+      data.step = data.step + 1;
+      getStepELe();
+    };
+
+    const endGuide = () => {
+      let guideInfo =
+        JSON.parse(localStorage.getItem('teacher-guideInfo')) || null;
+      if (!guideInfo) {
+        guideInfo = { classGuide: true };
+      } else {
+        guideInfo.classGuide = true;
+      }
+      localStorage.setItem('teacher-guideInfo', JSON.stringify(guideInfo));
+      tipShow.value = false;
+      //  localStorage.setItem('endC')
+    };
+    return () => (
+      <>
+        {tipShow.value ? (
+          <div
+            v-model:show={tipShow.value}
+            class={['n-modal-mask', 'n-modal-mask-guide']}>
+            <div class={styles.content} onClick={() => handleNext()}>
+              <div
+                class={styles.backBtn}
+                onClick={(e: Event) => {
+                  e.stopPropagation();
+                  endGuide();
+                }}>
+                跳过
+              </div>
+              <div
+                class={styles.box}
+                style={{...data.box,...data.steps[data.step].boxStyle}}
+                id={`modeType-${data.step}`}>
+
+                {data.steps.map((item: any, index) => (
+
+                  <div
+                    onClick={(e: Event) => e.stopPropagation()}
+                    class={styles.item}
+                    style={ item.type=='bottom'?  {
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                      top:`-${item.imgStyle?.height}`
+
+                    }:{
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                     top: `${data.box?.height}`,
+
+                    }}>
+                    <img
+                      class={styles.img}
+                      style={item.imgStyle}
+                      src={item.img}
+                    />
+                    {/* <img
+                      class={styles.iconHead}
+                      style={item.handStyle}
+                      src={getImage('indexDot.png')}
+                    /> */}
+                    <div class={styles.btns} style={item.btnsStyle}>
+                      {data.step + 1 == data.steps.length ? (
+                        <>
+                         <div
+                            class={[styles.endBtn]}
+                            onClick={() => endGuide()}>
+                            完成
+                          </div>
+                          <div
+                            class={styles.nextBtn}
+                            onClick={() => {
+                              data.step = 0;
+                              getStepELe();
+                            }}>
+                            再看一遍
+                          </div>
+
+                        </>
+                      ) : (
+                        <div class={styles.btn} onClick={() => handleNext()}>
+                          下一步 ({data.step + 1}/{data.steps.length})
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        ) : null}
+      </>
+    );
+  }
+});

+ 236 - 0
src/custom-plugins/guide-page/data-guide.tsx

@@ -0,0 +1,236 @@
+import { NButton } from 'naive-ui';
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import { getImage } from './images';
+import {px2vw,px2vwH} from '@/utils/index'
+export default defineComponent({
+  name: 'data-guide',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const data = reactive({
+      box: {
+        height:'0px',
+      } as any,
+      show: false,
+      /**
+       *
+            width:  px2vw(840),
+            height:  px2vw(295)
+       */
+      steps: [
+        {
+          ele: '',
+          eleRect: {} as DOMRect,
+          img: getImage('data1.png'),
+          handStyle: {
+            top: '0.91rem'
+          },
+          imgStyle: {
+            left: '-105px',
+            width:  '472px',
+            height:  '256px'
+          },
+          btnsStyle: {
+            bottom:'30px',
+            left: '-90px',
+          },
+          boxStyle:{
+            borderRadius:'25px'
+          },
+          eleRectPadding:{
+            left:14,
+            top:14,
+            width:28,
+            height:28
+          }
+        },
+        {
+          ele: '',
+          img: getImage('data2.png'),
+          imgStyle: {
+            left:  '-105px',
+            width: '509px',
+            height:'230px'
+          },
+          btnsStyle: {
+            bottom: '30px',
+            left: px2vw(-90),
+          },
+          boxStyle:{
+            borderRadius:'25px'
+          },
+          eleRectPadding:{
+            left:14,
+            top:14,
+            width:28,
+            height:28
+          }
+
+        },
+        {
+          ele: '',
+          img: getImage('data3.png'),
+          imgStyle: {
+            top: '100%',
+            left:  '-105px',
+            width: '438px',
+            height:'230px'
+          },
+          btnsStyle: {
+            bottom: '30px',
+            left: px2vw(-90),
+          },
+
+          boxStyle:{
+            borderRadius:'25px'
+          },
+          eleRectPadding:{
+            left:14,
+            top:14,
+            width:28,
+            height:28
+          }
+
+        },
+      ],
+      step: 0
+    });
+    const tipShow = ref(false);
+    const guideInfo = localStorage.getItem('teacher-guideInfo');
+    if (guideInfo && JSON.parse(guideInfo).dataGuide) {
+      tipShow.value = false;
+    } else {
+      tipShow.value = true;
+    }
+    const getStepELe = () => {
+
+      const ele: HTMLElement = document.getElementById(`data-${data.step}`)!;
+
+      if (ele) {
+        const eleRect = ele.getBoundingClientRect();
+
+        const left = data.steps[data.step].eleRectPadding?.left || 0;
+        const top = data.steps[data.step].eleRectPadding?.top || 0;
+        const width = data.steps[data.step].eleRectPadding?.width || 0;
+        const height = data.steps[data.step].eleRectPadding?.height || 0
+        data.box = {
+          left: eleRect.x - left+ 'px',
+          top: eleRect.y - top +'px',
+          width: eleRect.width + width+'px',
+          height: eleRect.height +height+ 'px'
+        };
+        console.log(`coai-${data.step}`,data.box);
+      }
+    };
+    onMounted(() => {
+      getStepELe();
+    });
+
+    const handleNext = () => {
+      if (data.step >= 4) {
+        endGuide();
+        return;
+      }
+      data.step = data.step + 1;
+      getStepELe();
+    };
+
+    const endGuide = () => {
+      let guideInfo =
+        JSON.parse(localStorage.getItem('teacher-guideInfo')) || null;
+      if (!guideInfo) {
+        guideInfo = { dataGuide: true };
+      } else {
+        guideInfo.dataGuide = true;
+      }
+      localStorage.setItem('teacher-guideInfo', JSON.stringify(guideInfo));
+      tipShow.value = false;
+      //  localStorage.setItem('endC')
+    };
+    return () => (
+      <>
+        {tipShow.value ? (
+          <div
+            v-model:show={tipShow.value}
+            class={['n-modal-mask', 'n-modal-mask-guide']}>
+            <div class={styles.content} onClick={() => handleNext()}>
+              <div
+                class={styles.backBtn}
+                onClick={(e: Event) => {
+                  e.stopPropagation();
+                  endGuide();
+                }}>
+                跳过
+              </div>
+              <div
+                class={styles.box}
+                style={{...data.box,...data.steps[data.step].boxStyle}}
+                id={`modeType-${data.step}`}>
+
+                {data.steps.map((item: any, index) => (
+
+                  <div
+                    onClick={(e: Event) => e.stopPropagation()}
+                    class={styles.item}
+                    style={ item.type=='bottom'?  {
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                      top:`-${item.imgStyle?.height}`
+
+                    }:{
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                     top: `${data.box?.height}`,
+
+                    }}>
+                    <img
+                      class={styles.img}
+                      style={item.imgStyle}
+                      src={item.img}
+                    />
+                    {/* <img
+                      class={styles.iconHead}
+                      style={item.handStyle}
+                      src={getImage('indexDot.png')}
+                    /> */}
+                    <div class={styles.btns} style={item.btnsStyle}>
+                      {data.step + 1 == data.steps.length ? (
+                        <>
+                         <div
+                            class={[styles.endBtn]}
+                            onClick={() => endGuide()}>
+                            完成
+                          </div>
+                          <div
+                            class={styles.nextBtn}
+                            onClick={() => {
+                              data.step = 0;
+                              getStepELe();
+                            }}>
+                            再看一遍
+                          </div>
+
+                        </>
+                      ) : (
+                        <div class={styles.btn} onClick={() => handleNext()}>
+                          下一步 ({data.step + 1}/{data.steps.length})
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        ) : null}
+      </>
+    );
+  }
+});

+ 58 - 28
src/custom-plugins/guide-page/home-guide.tsx

@@ -9,14 +9,15 @@ import {
 } from 'vue';
 import styles from './index.module.less';
 import { getImage } from './images';
-import {px2vw} from '@/utils/index'
-import { visibility } from 'html2canvas/dist/types/css/property-descriptors/visibility';
+import {px2vw,px2vwH} from '@/utils/index'
 export default defineComponent({
   name: 'coai-guide',
   emits: ['close'],
   setup(props, { emit }) {
     const data = reactive({
-      box: {},
+      box: {
+        height:'0px',
+      } as any,
       show: false,
       /**
        *
@@ -37,7 +38,7 @@ export default defineComponent({
             height:  px2vw(295)
           },
           btnsStyle: {
-            top:  px2vw(215),
+            bottom:'30px',
             left:  px2vw(540),
           },
           boxStyle:{
@@ -57,15 +58,14 @@ export default defineComponent({
         {
           ele: '',
           img: getImage('home2.png'),
-
           imgStyle: {
-            top: '51Px',
-            left: '-18em',
-            width:  px2vw(401),
-            height:  px2vw(227)
+            top: '100%',
+            left:  px2vw(-250),
+            width: px2vw(401),
+            height: px2vw(227)
           },
           btnsStyle: {
-            top:  px2vw(200),
+            bottom: '30px',
             left: px2vw(-90),
           },
           boxStyle:{
@@ -88,13 +88,13 @@ export default defineComponent({
             transform: 'rotate(180deg)'
           },
           imgStyle: {
-            top:px2vw(575),
+
             width:  px2vw(454),
             height:  px2vw(227),
             left:px2vw(282)
           },
           btnsStyle: {
-            top:px2vw(730),
+            bottom: '30px',
             left:px2vw(450)
           },
           eleRectPadding:{
@@ -113,14 +113,37 @@ export default defineComponent({
             transform: 'rotate(180deg)'
           },
           imgStyle: {
-            top: '4rem',
             left:  px2vw(-310),
             width:  px2vw(477),
             height:  px2vw(227),
           },
           btnsStyle: {
-            top: '-0.85rem',
-            left: '-3rem',
+            bottom: '30px',
+          },
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          }
+        },
+        {
+          ele: '',
+          img: getImage('home5.png'),
+          handStyle: {
+            top: '-1.39rem',
+            left: '1.4rem',
+            transform: 'rotate(180deg)'
+          },
+          imgStyle: {
+            top: '0',
+            width:  px2vw(610),
+            height:  px2vw(290),
+            left: px2vw(38)
+          },
+          btnsStyle: {
+            bottom: '80px',
+            left: px2vw(195),
             'justify-content': 'center',
             padding: 0
           },
@@ -129,14 +152,15 @@ export default defineComponent({
             top:7,
             width:14,
             height:14
-          }
+          },
+          type:'bottom'
         }
       ],
       step: 0
     });
     const tipShow = ref(false);
     const guideInfo = localStorage.getItem('teacher-guideInfo');
-    if (guideInfo && JSON.parse(guideInfo).coaiGuide) {
+    if (guideInfo && JSON.parse(guideInfo).homeGuide) {
       tipShow.value = false;
     } else {
       tipShow.value = true;
@@ -164,7 +188,7 @@ export default defineComponent({
     });
 
     const handleNext = () => {
-      if (data.step >= 3) {
+      if (data.step >= 4) {
         endGuide();
         return;
       }
@@ -205,13 +229,20 @@ export default defineComponent({
                 id={`modeType-${data.step}`}>
 
                 {data.steps.map((item: any, index) => (
+
                   <div
                     onClick={(e: Event) => e.stopPropagation()}
                     class={styles.item}
-                    style={{
+                    style={ item.type=='bottom'?  {
                       display: index === data.step ? '' : 'none',
                       left: `${item.eleRect?.left}px`,
-                      top: `${item.eleRect?.top}px`
+                      top:`-${item.imgStyle?.height}`
+
+                    }:{
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                     top: `${data.box?.height}`,
+
                     }}>
                     <img
                       class={styles.img}
@@ -226,21 +257,20 @@ export default defineComponent({
                     <div class={styles.btns} style={item.btnsStyle}>
                       {data.step + 1 == data.steps.length ? (
                         <>
+                         <div
+                            class={[styles.endBtn]}
+                            onClick={() => endGuide()}>
+                            完成
+                          </div>
                           <div
-                            class={styles.btn}
-                            color="transparent"
-                            style={{ 'border-color': '#fff' }}
+                            class={styles.nextBtn}
                             onClick={() => {
                               data.step = 0;
                               getStepELe();
                             }}>
                             再看一遍
                           </div>
-                          <div
-                            class={[styles.btn, styles.endBtn]}
-                            onClick={() => endGuide()}>
-                            完成
-                          </div>
+
                         </>
                       ) : (
                         <div class={styles.btn} onClick={() => handleNext()}>

BIN
src/custom-plugins/guide-page/images/attent1.png


BIN
src/custom-plugins/guide-page/images/attent2.png


BIN
src/custom-plugins/guide-page/images/attent3.png


BIN
src/custom-plugins/guide-page/images/attent4.png


BIN
src/custom-plugins/guide-page/images/class1.png


BIN
src/custom-plugins/guide-page/images/class2.png


BIN
src/custom-plugins/guide-page/images/class3.png


BIN
src/custom-plugins/guide-page/images/data1.png


BIN
src/custom-plugins/guide-page/images/data2.png


BIN
src/custom-plugins/guide-page/images/data3.png


BIN
src/custom-plugins/guide-page/images/endBtn.png


BIN
src/custom-plugins/guide-page/images/lessons1.png


BIN
src/custom-plugins/guide-page/images/lessons2.png


BIN
src/custom-plugins/guide-page/images/lessons3.png


BIN
src/custom-plugins/guide-page/images/lessons4.png


BIN
src/custom-plugins/guide-page/images/lessons5.png


BIN
src/custom-plugins/guide-page/images/music1.png


BIN
src/custom-plugins/guide-page/images/music2.png


BIN
src/custom-plugins/guide-page/images/music3.png


BIN
src/custom-plugins/guide-page/images/myColloge1.png


BIN
src/custom-plugins/guide-page/images/myResourecs1.png


BIN
src/custom-plugins/guide-page/images/shareResources1.png


BIN
src/custom-plugins/guide-page/images/shareResources2.png


BIN
src/custom-plugins/guide-page/images/student1.png


BIN
src/custom-plugins/guide-page/images/student2.png


BIN
src/custom-plugins/guide-page/images/teacher1.png


BIN
src/custom-plugins/guide-page/images/teacher2.png


BIN
src/custom-plugins/guide-page/images/train1.png


BIN
src/custom-plugins/guide-page/images/train2.png


+ 39 - 8
src/custom-plugins/guide-page/index.module.less

@@ -23,16 +23,17 @@
 
 .backBtn {
   position: absolute;
-  right: 20px;
-  top: 20px;
+  right: 20Px;
+  top: 20Px;
   font-size: 14px;
-  line-height: 24px;
-  padding: 0 14px;
+  line-height: 24Px;
+  padding: 0 14Px;
   border-radius: 17px;
   border: 1px solid #ffffff;
   color: #fff;
   text-align: center;
   z-index: 2001;
+  cursor: pointer;
 
   &:active {
     opacity: 0.8;
@@ -71,7 +72,7 @@
   z-index: 10;
 
   .img {
-    position: absolute;
+    position: relative;
     width: 100%;
   }
 
@@ -90,11 +91,9 @@
   .btns {
     position: absolute;
     display: flex;
-    width: 100%;
     padding: 0 12px;
 
     .btn {
-
       background: url('./images/numbrBtn.png');
       background-size: 144px 49px;
       width: 144px;
@@ -112,7 +111,39 @@
     }
 
     .endBtn {
-      margin-left: 5px;
+      width: 144px;
+      height: 49px;
+      margin-right: 20px;
+      background: url('./images/endBtn.png');
+      background-size: 144px 49px;
+
+      line-height: 49px;
+      font-size: 18px;
+      padding: 0;
+      cursor: pointer;
+      text-align: center;
+      color: #fff;
+
+      &:hover {
+        opacity: .8;
+      }
+    }
+
+    .nextBtn {
+      width: 124px;
+      height: 49px;
+      background: url('./images/nextBtn.png') no-repeat;
+      background-size: 124px 49px;
+      line-height: 49px;
+      font-size: 18px;
+      padding: 0;
+      cursor: pointer;
+      text-align: center;
+      color: #fff;
+
+      &:hover {
+        opacity: .8;
+      }
     }
   }
 }

+ 279 - 0
src/custom-plugins/guide-page/lessons-guide.tsx

@@ -0,0 +1,279 @@
+import { NButton } from 'naive-ui';
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import { getImage } from './images';
+import {px2vw,px2vwH} from '@/utils/index'
+export default defineComponent({
+  name: 'lessons-guide',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const data = reactive({
+      box: {
+        height:'0px',
+      } as any,
+      show: false,
+      /**
+       *
+            width:  px2vw(840),
+            height:  px2vw(295)
+       */
+      steps: [
+        {
+          ele: '',
+          eleRect: {} as DOMRect,
+          img: getImage('lessons1.png'),
+          handStyle: {
+            top: '0.91rem'
+          },
+          imgStyle: {
+            left: '-4.4em',
+            width:  px2vw(420),
+            height:  px2vw(228)
+          },
+          btnsStyle: {
+            bottom:'30px',
+            left:  px2vw(90),
+          },
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          }
+        },
+        {
+          ele: '',
+          img: getImage('lessons2.png'),
+          imgStyle: {
+            top:`0px`,
+            left:'-500px',
+            width:'647px',
+            height:'223px'
+          },
+          btnsStyle: {
+            bottom: '30px',
+            left: px2vw(-90),
+          },
+
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          },
+          type:'left'
+
+        },
+        {
+          ele: '',
+          img: getImage('lessons3.png'),
+          imgStyle: {
+            top: '100%',
+            left:  '-265px',
+            width: '515px',
+            height:'227px'
+          },
+          btnsStyle: {
+            bottom: '30px',
+            left: px2vw(-90),
+          },
+
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          },
+          type:'left'
+
+        },
+        {
+          ele: '',
+          img: getImage('lessons4.png'),
+          imgStyle: {
+            top: '100%',
+            left:  '-265px',
+            width: '515px',
+            height:'227px'
+          },
+          btnsStyle: {
+            bottom: '30px',
+            left: px2vw(-90),
+          },
+
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          }
+
+        },
+        {
+          ele: '',
+          img: getImage('lessons5.png'),
+          imgStyle: {
+            top: '100%',
+            left:  '-290px',
+            width: '648px',
+            height:'228px'
+          },
+          btnsStyle: {
+            bottom: '30px',
+            left: px2vw(-90),
+          },
+
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          }
+
+        },
+      ],
+      step: 0
+    });
+    const tipShow = ref(false);
+    const guideInfo = localStorage.getItem('teacher-guideInfo');
+    if (guideInfo && JSON.parse(guideInfo).lessonsGuide) {
+      tipShow.value = false;
+    } else {
+      tipShow.value = true;
+    }
+    const getStepELe = () => {
+
+      const ele: HTMLElement = document.getElementById(`lessons-${data.step}`)!;
+
+      if (ele) {
+        const eleRect = ele.getBoundingClientRect();
+
+        const left = data.steps[data.step].eleRectPadding?.left || 0;
+        const top = data.steps[data.step].eleRectPadding?.top || 0;
+        const width = data.steps[data.step].eleRectPadding?.width || 0;
+        const height = data.steps[data.step].eleRectPadding?.height || 0
+        data.box = {
+          left: eleRect.x - left+ 'px',
+          top: eleRect.y - top +'px',
+          width: eleRect.width + width+'px',
+          height: eleRect.height +height+ 'px'
+        };
+        console.log(`coai-${data.step}`,data.box);
+      }
+    };
+    onMounted(() => {
+      getStepELe();
+    });
+
+    const handleNext = () => {
+      if (data.step >= 4) {
+        endGuide();
+        return;
+      }
+      data.step = data.step + 1;
+      getStepELe();
+    };
+
+    const endGuide = () => {
+      let guideInfo =
+        JSON.parse(localStorage.getItem('teacher-guideInfo')) || null;
+      if (!guideInfo) {
+        guideInfo = { lessonsGuide: true };
+      } else {
+        guideInfo.lessonsGuide = true;
+      }
+      localStorage.setItem('teacher-guideInfo', JSON.stringify(guideInfo));
+      tipShow.value = false;
+      //  localStorage.setItem('endC')
+    };
+    return () => (
+      <>
+        {tipShow.value ? (
+          <div
+            v-model:show={tipShow.value}
+            class={['n-modal-mask', 'n-modal-mask-guide']}>
+            <div class={styles.content} onClick={() => handleNext()}>
+              <div
+                class={styles.backBtn}
+                onClick={(e: Event) => {
+                  e.stopPropagation();
+                  endGuide();
+                }}>
+                跳过
+              </div>
+              <div
+                class={styles.box}
+                style={{...data.box,...data.steps[data.step].boxStyle}}
+                id={`modeType-${data.step}`}>
+
+                {data.steps.map((item: any, index) => (
+
+                  <div
+                    onClick={(e: Event) => e.stopPropagation()}
+                    class={styles.item}
+                    style={ item.type=='bottom'?  {
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                      top:`-${item.imgStyle?.height}`
+                    }: item.type=='left'?{
+                      display: index === data.step ? '' : 'none',
+                      left: `${ parseFloat(data.box?.width)/2-parseFloat(item.imgStyle?.width)/2-14}px`,
+                     top: `${ parseFloat(data.box?.height)/2-parseFloat(item.imgStyle?.height)/20+14}px`,
+                    }:{
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                     top: `${data.box?.height}`,
+
+                    }}>
+
+                    <img
+                      class={styles.img}
+                      style={item.imgStyle}
+                      src={item.img}
+                    />
+                    {/* <img
+                      class={styles.iconHead}
+                      style={item.handStyle}
+                      src={getImage('indexDot.png')}
+                    /> */}
+                    <div class={styles.btns} style={item.btnsStyle}>
+                      {data.step + 1 == data.steps.length ? (
+                        <>
+                         <div
+                            class={[styles.endBtn]}
+                            onClick={() => endGuide()}>
+                            完成
+                          </div>
+                          <div
+                            class={styles.nextBtn}
+                            onClick={() => {
+                              data.step = 0;
+                              getStepELe();
+                            }}>
+                            再看一遍
+                          </div>
+
+                        </>
+                      ) : (
+                        <div class={styles.btn} onClick={() => handleNext()}>
+                          下一步 ({data.step + 1}/{data.steps.length})
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        ) : null}
+      </>
+    );
+  }
+});

+ 229 - 0
src/custom-plugins/guide-page/music-guide.tsx

@@ -0,0 +1,229 @@
+import { NButton } from 'naive-ui';
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import { getImage } from './images';
+import {px2vw,px2vwH} from '@/utils/index'
+export default defineComponent({
+  name: 'music-guide',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const data = reactive({
+      box: {
+        height:'0px',
+      } as any,
+      show: false,
+      /**
+       *
+            width:  px2vw(840),
+            height:  px2vw(295)
+       */
+      steps: [
+        {
+          ele: '',
+          eleRect: {} as DOMRect,
+          img: getImage('music1.png'),
+          handStyle: {
+            top: '0.91rem'
+          },
+          imgStyle: {
+            left: '-4.4em',
+            width:  '534px',
+            height:  '228px'
+          },
+          btnsStyle: {
+            bottom:'30px',
+            left: '90px',
+          },
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          }
+        },
+        {
+          ele: '',
+          img: getImage('music2.png'),
+          imgStyle: {
+            top: '100%',
+            left:  '-205px',
+            width: '420px',
+            height:'228px'
+          },
+          btnsStyle: {
+            bottom: '30px',
+            left: px2vw(-60),
+          },
+
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          }
+
+        },
+        {
+          ele: '',
+          img: getImage('music3.png'),
+          imgStyle: {
+            top: '100%',
+            left:  '-130px',
+            width: '401px',
+            height:'304px'
+          },
+          btnsStyle: {
+            bottom: '90px',
+            left:'16px',
+          },
+
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          },
+          type:'bottom'
+        },
+      ],
+      step: 0
+    });
+    const tipShow = ref(false);
+    const guideInfo = localStorage.getItem('teacher-guideInfo');
+    if (guideInfo && JSON.parse(guideInfo).musicGuide) {
+      tipShow.value = false;
+    } else {
+      tipShow.value = true;
+    }
+    const getStepELe = () => {
+
+      const ele: HTMLElement = document.getElementById(`music-${data.step}`)!;
+
+      if (ele) {
+        const eleRect = ele.getBoundingClientRect();
+
+        const left = data.steps[data.step].eleRectPadding?.left || 0;
+        const top = data.steps[data.step].eleRectPadding?.top || 0;
+        const width = data.steps[data.step].eleRectPadding?.width || 0;
+        const height = data.steps[data.step].eleRectPadding?.height || 0
+        data.box = {
+          left: eleRect.x - left+ 'px',
+          top: eleRect.y - top +'px',
+          width: eleRect.width + width+'px',
+          height: eleRect.height +height+ 'px'
+        };
+        console.log(`coai-${data.step}`,data.box);
+      }
+    };
+    onMounted(() => {
+      getStepELe();
+    });
+
+    const handleNext = () => {
+      if (data.step >= 4) {
+        endGuide();
+        return;
+      }
+      data.step = data.step + 1;
+      getStepELe();
+    };
+
+    const endGuide = () => {
+      let guideInfo =
+        JSON.parse(localStorage.getItem('teacher-guideInfo')) || null;
+      if (!guideInfo) {
+        guideInfo = { musicGuide: true };
+      } else {
+        guideInfo.musicGuide = true;
+      }
+      localStorage.setItem('teacher-guideInfo', JSON.stringify(guideInfo));
+      tipShow.value = false;
+      //  localStorage.setItem('endC')
+    };
+    return () => (
+      <>
+        {tipShow.value ? (
+          <div
+            v-model:show={tipShow.value}
+            class={['n-modal-mask', 'n-modal-mask-guide']}>
+            <div class={styles.content} onClick={() => handleNext()}>
+              <div
+                class={styles.backBtn}
+                onClick={(e: Event) => {
+                  e.stopPropagation();
+                  endGuide();
+                }}>
+                跳过
+              </div>
+              <div
+                class={styles.box}
+                style={{...data.box,...data.steps[data.step].boxStyle}}
+                id={`modeType-${data.step}`}>
+
+                {data.steps.map((item: any, index) => (
+
+                  <div
+                    onClick={(e: Event) => e.stopPropagation()}
+                    class={styles.item}
+                    style={ item.type=='bottom'?  {
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                      top:`-${item.imgStyle?.height}`
+
+                    }:{
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                     top: `${data.box?.height}`,
+
+                    }}>
+                    <img
+                      class={styles.img}
+                      style={item.imgStyle}
+                      src={item.img}
+                    />
+                    {/* <img
+                      class={styles.iconHead}
+                      style={item.handStyle}
+                      src={getImage('indexDot.png')}
+                    /> */}
+                    <div class={styles.btns} style={item.btnsStyle}>
+                      {data.step + 1 == data.steps.length ? (
+                        <>
+                         <div
+                            class={[styles.endBtn]}
+                            onClick={() => endGuide()}>
+                            完成
+                          </div>
+                          <div
+                            class={styles.nextBtn}
+                            onClick={() => {
+                              data.step = 0;
+                              getStepELe();
+                            }}>
+                            再看一遍
+                          </div>
+
+                        </>
+                      ) : (
+                        <div class={styles.btn} onClick={() => handleNext()}>
+                          下一步 ({data.step + 1}/{data.steps.length})
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        ) : null}
+      </>
+    );
+  }
+});

+ 187 - 0
src/custom-plugins/guide-page/myColloge-guide.tsx

@@ -0,0 +1,187 @@
+import { NButton } from 'naive-ui';
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import { getImage } from './images';
+import {px2vw} from '@/utils/index'
+
+export default defineComponent({
+  name: 'myColloge-guide',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const data = reactive({
+      box: {
+        height:'0px',
+      } as any,
+      show: false,
+      /**
+       *
+            width:  px2vw(840),
+            height:  px2vw(295)
+       */
+      steps: [
+        {
+          ele: '',
+          img: getImage('myColloge1.png'),
+          imgStyle: {
+            top: '100%',
+            left:  '630px',
+            width: '458px',
+            height:'291px'
+          },
+          btnsStyle: {
+            bottom: '96px',
+            left: '780px',
+          },
+
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          },
+          type:'bottom'
+
+        },
+
+      ],
+      step: 0
+    });
+    const tipShow = ref(false);
+    const guideInfo = localStorage.getItem('teacher-guideInfo');
+    if (guideInfo && JSON.parse(guideInfo).myColloge) {
+      tipShow.value = false;
+    } else {
+      tipShow.value = true;
+    }
+    const getStepELe = () => {
+
+      const ele: HTMLElement = document.getElementById(`myColloge-${data.step}`)!;
+      if (ele) {
+        const eleRect = ele.getBoundingClientRect();
+
+        const left = data.steps[data.step].eleRectPadding?.left || 0;
+        const top = data.steps[data.step].eleRectPadding?.top || 0;
+        const width = data.steps[data.step].eleRectPadding?.width || 0;
+        const height = data.steps[data.step].eleRectPadding?.height || 0
+        data.box = {
+          left: eleRect.x - left+ 'px',
+          top: eleRect.y - top +'px',
+          width: eleRect.width + width+'px',
+          height: eleRect.height +height+ 'px'
+        };
+        console.log(`coai-${data.step}`,data.box);
+      }
+    };
+    onMounted(() => {
+      getStepELe();
+    });
+
+    const handleNext = () => {
+      if (data.step >= 4) {
+        endGuide();
+        return;
+      }
+      data.step = data.step + 1;
+      getStepELe();
+    };
+
+    const endGuide = () => {
+      let guideInfo =
+        JSON.parse(localStorage.getItem('teacher-guideInfo')) || null;
+      if (!guideInfo) {
+        guideInfo = { myColloge: true };
+      } else {
+        guideInfo.myColloge = true;
+      }
+      localStorage.setItem('teacher-guideInfo', JSON.stringify(guideInfo));
+      tipShow.value = false;
+      //  localStorage.setItem('endC')
+    };
+    return () => (
+      <>
+        {tipShow.value ? (
+          <div
+            v-model:show={tipShow.value}
+            class={['n-modal-mask', 'n-modal-mask-guide']}>
+            <div class={styles.content} onClick={() => handleNext()}>
+              <div
+                class={styles.backBtn}
+                onClick={(e: Event) => {
+                  e.stopPropagation();
+                  endGuide();
+                }}>
+                跳过
+              </div>
+              <div
+                class={styles.box}
+                style={{...data.box,...data.steps[data.step].boxStyle}}
+                id={`modeType-${data.step}`}>
+
+                {data.steps.map((item: any, index) => (
+
+                  <div
+                    onClick={(e: Event) => e.stopPropagation()}
+                    class={styles.item}
+                    style={ item.type=='bottom'?  {
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                      top:`-${item.imgStyle?.height}`
+
+                    }:{
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                     top: `${data.box?.height}`,
+
+                    }}>
+                    <img
+                      class={styles.img}
+                      style={item.imgStyle}
+                      src={item.img}
+                    />
+                    {/* <img
+                      class={styles.iconHead}
+                      style={item.handStyle}
+                      src={getImage('indexDot.png')}
+                    /> */}
+                    <div class={styles.btns} style={item.btnsStyle}>
+                      {data.step + 1 == data.steps.length ? (
+                        <>
+                         <div
+                            class={[styles.endBtn]}
+                            onClick={() => endGuide()}>
+                            完成
+                          </div>
+                          {/* <div
+                            class={styles.nextBtn}
+                            onClick={() => {
+                              data.step = 0;
+                              getStepELe();
+                            }}>
+                            再看一遍
+                          </div> */}
+
+                        </>
+                      ) : (
+                        <div class={styles.btn} onClick={() => handleNext()}>
+                          下一步 ({data.step + 1}/{data.steps.length})
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        ) : null}
+
+      </>
+    );
+  }
+});

+ 190 - 0
src/custom-plugins/guide-page/myResources-guide.tsx

@@ -0,0 +1,190 @@
+import { NButton } from 'naive-ui';
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import { getImage } from './images';
+import {px2vw} from '@/utils/index'
+
+export default defineComponent({
+  name: 'myResources-guide',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const data = reactive({
+      box: {
+        height:'0px',
+      } as any,
+      show: false,
+      /**
+       *
+            width:  px2vw(840),
+            height:  px2vw(295)
+       */
+      steps: [
+        {
+          ele: '',
+          eleRect: {} as DOMRect,
+          img: getImage('myResourecs1.png'),
+          handStyle: {
+            top: '0.91rem'
+          },
+          imgStyle: {
+            left: '-263px',
+            width:  '556px',
+            height:  '257px'
+          },
+          btnsStyle: {
+            bottom:'30px',
+            left: '-90px',
+          },
+          eleRectPadding:{
+            left:14,
+            top:14,
+            width:28,
+            height:28
+          },
+          boxStyle:{
+            borderRadius: '30px'
+          },
+        },
+
+      ],
+      step: 0
+    });
+    const tipShow = ref(false);
+    const guideInfo = localStorage.getItem('teacher-guideInfo');
+    if (guideInfo && JSON.parse(guideInfo).myResourcesGuide) {
+      tipShow.value = false;
+    } else {
+      tipShow.value = true;
+    }
+    const getStepELe = () => {
+
+      const ele: HTMLElement = document.getElementById(`myResources-${data.step}`)!;
+
+      if (ele) {
+        const eleRect = ele.getBoundingClientRect();
+
+        const left = data.steps[data.step].eleRectPadding?.left || 0;
+        const top = data.steps[data.step].eleRectPadding?.top || 0;
+        const width = data.steps[data.step].eleRectPadding?.width || 0;
+        const height = data.steps[data.step].eleRectPadding?.height || 0
+        data.box = {
+          left: eleRect.x - left+ 'px',
+          top: eleRect.y - top +'px',
+          width: eleRect.width + width+'px',
+          height: eleRect.height +height+ 'px'
+        };
+        console.log(`coai-${data.step}`,data.box);
+      }
+    };
+    onMounted(() => {
+      getStepELe();
+    });
+
+    const handleNext = () => {
+      if (data.step >= 4) {
+        endGuide();
+        return;
+      }
+      data.step = data.step + 1;
+      getStepELe();
+    };
+
+    const endGuide = () => {
+      let guideInfo =
+        JSON.parse(localStorage.getItem('teacher-guideInfo')) || null;
+      if (!guideInfo) {
+        guideInfo = { myResourcesGuide: true };
+      } else {
+        guideInfo.myResourcesGuide = true;
+      }
+      localStorage.setItem('teacher-guideInfo', JSON.stringify(guideInfo));
+      tipShow.value = false;
+      //  localStorage.setItem('endC')
+    };
+    return () => (
+      <>
+        {tipShow.value ? (
+          <div
+            v-model:show={tipShow.value}
+            class={['n-modal-mask', 'n-modal-mask-guide']}>
+            <div class={styles.content} onClick={() => handleNext()}>
+              <div
+                class={styles.backBtn}
+                onClick={(e: Event) => {
+                  e.stopPropagation();
+                  endGuide();
+                }}>
+                跳过
+              </div>
+              <div
+                class={styles.box}
+                style={{...data.box,...data.steps[data.step].boxStyle}}
+                id={`modeType-${data.step}`}>
+
+                {data.steps.map((item: any, index) => (
+
+                  <div
+                    onClick={(e: Event) => e.stopPropagation()}
+                    class={styles.item}
+                    style={ item.type=='bottom'?  {
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                      top:`-${item.imgStyle?.height}`
+
+                    }:{
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                     top: `${data.box?.height}`,
+
+                    }}>
+                    <img
+                      class={styles.img}
+                      style={item.imgStyle}
+                      src={item.img}
+                    />
+                    {/* <img
+                      class={styles.iconHead}
+                      style={item.handStyle}
+                      src={getImage('indexDot.png')}
+                    /> */}
+                    <div class={styles.btns} style={item.btnsStyle}>
+                      {data.step + 1 == data.steps.length ? (
+                        <>
+                         <div
+                            class={[styles.endBtn]}
+                            onClick={() => endGuide()}>
+                            完成
+                          </div>
+                          {/* <div
+                            class={styles.nextBtn}
+                            onClick={() => {
+                              data.step = 0;
+                              getStepELe();
+                            }}>
+                            再看一遍
+                          </div> */}
+
+                        </>
+                      ) : (
+                        <div class={styles.btn} onClick={() => handleNext()}>
+                          下一步 ({data.step + 1}/{data.steps.length})
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        ) : null}
+      </>
+    );
+  }
+});

+ 212 - 0
src/custom-plugins/guide-page/shareResources-guide.tsx

@@ -0,0 +1,212 @@
+import { NButton } from 'naive-ui';
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import { getImage } from './images';
+import {px2vw} from '@/utils/index'
+
+export default defineComponent({
+  name: 'shareResources-guide',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const data = reactive({
+      box: {
+        height:'0px',
+      } as any,
+      show: false,
+      /**
+       *
+            width:  px2vw(840),
+            height:  px2vw(295)
+       */
+      steps: [
+        {
+          ele: '',
+          eleRect: {} as DOMRect,
+          img: getImage('shareResources1.png'),
+          handStyle: {
+            top: '0.91rem'
+          },
+          imgStyle: {
+            left: '-263px',
+            width:  '553px',
+            height:  '229px'
+          },
+          btnsStyle: {
+            bottom:'30px',
+            left: '-90px',
+          },
+          eleRectPadding:{
+            left:14,
+            top:14,
+            width:28,
+            height:28
+          },
+          boxStyle:{
+            borderRadius: '30px'
+          },
+        },
+        {
+          ele: '',
+          img: getImage('shareResources2.png'),
+          imgStyle: {
+            top: '100%',
+            left:  '630px',
+            width: '515px',
+            height:'227px'
+          },
+          btnsStyle: {
+            bottom: '75px',
+            left: '724px',
+          },
+
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          },
+          type:'bottom'
+
+        },
+      ],
+      step: 0
+    });
+    const tipShow = ref(false);
+    const guideInfo = localStorage.getItem('teacher-guideInfo');
+    if (guideInfo && JSON.parse(guideInfo).shareResourcesGuide) {
+      tipShow.value = false;
+    } else {
+      tipShow.value = true;
+    }
+    const getStepELe = () => {
+
+      const ele: HTMLElement = document.getElementById(`shareResources-${data.step}`)!;
+
+      if (ele) {
+        const eleRect = ele.getBoundingClientRect();
+
+        const left = data.steps[data.step].eleRectPadding?.left || 0;
+        const top = data.steps[data.step].eleRectPadding?.top || 0;
+        const width = data.steps[data.step].eleRectPadding?.width || 0;
+        const height = data.steps[data.step].eleRectPadding?.height || 0
+        data.box = {
+          left: eleRect.x - left+ 'px',
+          top: eleRect.y - top +'px',
+          width: eleRect.width + width+'px',
+          height: eleRect.height +height+ 'px'
+        };
+        console.log(`coai-${data.step}`,data.box);
+      }
+    };
+    onMounted(() => {
+      getStepELe();
+    });
+
+    const handleNext = () => {
+      if (data.step >= 4) {
+        endGuide();
+        return;
+      }
+      data.step = data.step + 1;
+      getStepELe();
+    };
+
+    const endGuide = () => {
+      let guideInfo =
+        JSON.parse(localStorage.getItem('teacher-guideInfo')) || null;
+      if (!guideInfo) {
+        guideInfo = { shareResourcesGuide: true };
+      } else {
+        guideInfo.shareResourcesGuide = true;
+      }
+      localStorage.setItem('teacher-guideInfo', JSON.stringify(guideInfo));
+      tipShow.value = false;
+      //  localStorage.setItem('endC')
+    };
+    return () => (
+      <>
+        {tipShow.value ? (
+          <div
+            v-model:show={tipShow.value}
+            class={['n-modal-mask', 'n-modal-mask-guide']}>
+            <div class={styles.content} onClick={() => handleNext()}>
+              <div
+                class={styles.backBtn}
+                onClick={(e: Event) => {
+                  e.stopPropagation();
+                  endGuide();
+                }}>
+                跳过
+              </div>
+              <div
+                class={styles.box}
+                style={{...data.box,...data.steps[data.step].boxStyle}}
+                id={`modeType-${data.step}`}>
+
+                {data.steps.map((item: any, index) => (
+
+                  <div
+                    onClick={(e: Event) => e.stopPropagation()}
+                    class={styles.item}
+                    style={ item.type=='bottom'?  {
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                      top:`-${item.imgStyle?.height}`
+
+                    }:{
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                     top: `${data.box?.height}`,
+
+                    }}>
+                    <img
+                      class={styles.img}
+                      style={item.imgStyle}
+                      src={item.img}
+                    />
+                    {/* <img
+                      class={styles.iconHead}
+                      style={item.handStyle}
+                      src={getImage('indexDot.png')}
+                    /> */}
+                    <div class={styles.btns} style={item.btnsStyle}>
+                      {data.step + 1 == data.steps.length ? (
+                        <>
+                         <div
+                            class={[styles.endBtn]}
+                            onClick={() => endGuide()}>
+                            完成
+                          </div>
+                          <div
+                            class={styles.nextBtn}
+                            onClick={() => {
+                              data.step = 0;
+                              getStepELe();
+                            }}>
+                            再看一遍
+                          </div>
+
+                        </>
+                      ) : (
+                        <div class={styles.btn} onClick={() => handleNext()}>
+                          下一步 ({data.step + 1}/{data.steps.length})
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        ) : null}
+      </>
+    );
+  }
+});

+ 207 - 0
src/custom-plugins/guide-page/student-guide.tsx

@@ -0,0 +1,207 @@
+import { NButton } from 'naive-ui';
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import { getImage } from './images';
+import {px2vw,px2vwH} from '@/utils/index'
+export default defineComponent({
+  name: 'coai-guide',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const data = reactive({
+      box: {
+        height:'0px',
+      } as any,
+      show: false,
+      /**
+       *
+            width:  px2vw(840),
+            height:  px2vw(295)
+       */
+      steps: [
+        {
+          ele: '',
+          eleRect: {} as DOMRect,
+          img: getImage('student1.png'),
+          handStyle: {
+            top: '0.91rem'
+          },
+          imgStyle: {
+            left: '-4.4em',
+            width:  '518px',
+            height:  '256px'
+          },
+          btnsStyle: {
+            bottom:'30px',
+            left: '-90px',
+          },
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          }
+        },
+        {
+          ele: '',
+          img: getImage('student2.png'),
+          imgStyle: {
+            top: '100%',
+            left:  '-265px',
+            width: '515px',
+            height:'227px'
+          },
+          btnsStyle: {
+            bottom: '30px',
+            left: px2vw(-90),
+          },
+
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          }
+
+        },
+      ],
+      step: 0
+    });
+    const tipShow = ref(false);
+    const guideInfo = localStorage.getItem('teacher-guideInfo');
+    if (guideInfo && JSON.parse(guideInfo).studentGuide) {
+      tipShow.value = false;
+    } else {
+      tipShow.value = true;
+    }
+    const getStepELe = () => {
+
+      const ele: HTMLElement = document.getElementById(`student-${data.step}`)!;
+
+      if (ele) {
+        const eleRect = ele.getBoundingClientRect();
+
+        const left = data.steps[data.step].eleRectPadding?.left || 0;
+        const top = data.steps[data.step].eleRectPadding?.top || 0;
+        const width = data.steps[data.step].eleRectPadding?.width || 0;
+        const height = data.steps[data.step].eleRectPadding?.height || 0
+        data.box = {
+          left: eleRect.x - left+ 'px',
+          top: eleRect.y - top +'px',
+          width: eleRect.width + width+'px',
+          height: eleRect.height +height+ 'px'
+        };
+        console.log(`coai-${data.step}`,data.box);
+      }
+    };
+    onMounted(() => {
+      getStepELe();
+    });
+
+    const handleNext = () => {
+      if (data.step >= 4) {
+        endGuide();
+        return;
+      }
+      data.step = data.step + 1;
+      getStepELe();
+    };
+
+    const endGuide = () => {
+      let guideInfo =
+        JSON.parse(localStorage.getItem('teacher-guideInfo')) || null;
+      if (!guideInfo) {
+        guideInfo = { studentGuide: true };
+      } else {
+        guideInfo.studentGuide = true;
+      }
+      localStorage.setItem('teacher-guideInfo', JSON.stringify(guideInfo));
+      tipShow.value = false;
+      //  localStorage.setItem('endC')
+    };
+    return () => (
+      <>
+        {tipShow.value ? (
+          <div
+            v-model:show={tipShow.value}
+            class={['n-modal-mask', 'n-modal-mask-guide']}>
+            <div class={styles.content} onClick={() => handleNext()}>
+              <div
+                class={styles.backBtn}
+                onClick={(e: Event) => {
+                  e.stopPropagation();
+                  endGuide();
+                }}>
+                跳过
+              </div>
+              <div
+                class={styles.box}
+                style={{...data.box,...data.steps[data.step].boxStyle}}
+                id={`modeType-${data.step}`}>
+
+                {data.steps.map((item: any, index) => (
+
+                  <div
+                    onClick={(e: Event) => e.stopPropagation()}
+                    class={styles.item}
+                    style={ item.type=='bottom'?  {
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                      top:`-${item.imgStyle?.height}`
+
+                    }:{
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                     top: `${data.box?.height}`,
+
+                    }}>
+                    <img
+                      class={styles.img}
+                      style={item.imgStyle}
+                      src={item.img}
+                    />
+                    {/* <img
+                      class={styles.iconHead}
+                      style={item.handStyle}
+                      src={getImage('indexDot.png')}
+                    /> */}
+                    <div class={styles.btns} style={item.btnsStyle}>
+                      {data.step + 1 == data.steps.length ? (
+                        <>
+                         <div
+                            class={[styles.endBtn]}
+                            onClick={() => endGuide()}>
+                            完成
+                          </div>
+                          <div
+                            class={styles.nextBtn}
+                            onClick={() => {
+                              data.step = 0;
+                              getStepELe();
+                            }}>
+                            再看一遍
+                          </div>
+
+                        </>
+                      ) : (
+                        <div class={styles.btn} onClick={() => handleNext()}>
+                          下一步 ({data.step + 1}/{data.steps.length})
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        ) : null}
+      </>
+    );
+  }
+});

+ 211 - 0
src/custom-plugins/guide-page/teacher-guide.tsx

@@ -0,0 +1,211 @@
+import { NButton } from 'naive-ui';
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import { getImage } from './images';
+import {px2vw,px2vwH} from '@/utils/index'
+export default defineComponent({
+  name: 'teacher-guide',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const data = reactive({
+      box: {
+        height:'0px',
+      } as any,
+      show: false,
+      /**
+       *
+            width:  px2vw(840),
+            height:  px2vw(295)
+       */
+      steps: [
+        {
+          ele: '',
+          eleRect: {} as DOMRect,
+          img: getImage('teacher1.png'),
+          handStyle: {
+            top: '0.91rem'
+          },
+          imgStyle: {
+            left: '-70px',
+            width:  '472px',
+            height:  '230px'
+          },
+          btnsStyle: {
+            bottom:'30px',
+            left: '-90px',
+          },
+          boxStyle:{
+            borderRadius:'25px'
+          },
+          eleRectPadding:{
+            left:14,
+            top:14,
+            width:28,
+            height:28
+          }
+        },
+        {
+          ele: '',
+          img: getImage('teacher2.png'),
+          imgStyle: {
+            left:  '-168px',
+            width: '599px',
+            height:'230px'
+          },
+          btnsStyle: {
+            bottom: '30px',
+            left: px2vw(-90),
+          },
+          boxStyle:{
+            borderRadius:'25px'
+          },
+          eleRectPadding:{
+            left:14,
+            top:14,
+            width:28,
+            height:28
+          }
+
+        },
+      ],
+      step: 0
+    });
+    const tipShow = ref(false);
+    const guideInfo = localStorage.getItem('teacher-guideInfo');
+    if (guideInfo && JSON.parse(guideInfo).teacherGuide) {
+      tipShow.value = false;
+    } else {
+      tipShow.value = true;
+    }
+    const getStepELe = () => {
+
+      const ele: HTMLElement = document.getElementById(`teacher-${data.step}`)!;
+
+      if (ele) {
+        const eleRect = ele.getBoundingClientRect();
+
+        const left = data.steps[data.step].eleRectPadding?.left || 0;
+        const top = data.steps[data.step].eleRectPadding?.top || 0;
+        const width = data.steps[data.step].eleRectPadding?.width || 0;
+        const height = data.steps[data.step].eleRectPadding?.height || 0
+        data.box = {
+          left: eleRect.x - left+ 'px',
+          top: eleRect.y - top +'px',
+          width: eleRect.width + width+'px',
+          height: eleRect.height +height+ 'px'
+        };
+        console.log(`coai-${data.step}`,data.box);
+      }
+    };
+    onMounted(() => {
+      getStepELe();
+    });
+
+    const handleNext = () => {
+      if (data.step >= 4) {
+        endGuide();
+        return;
+      }
+      data.step = data.step + 1;
+      getStepELe();
+    };
+
+    const endGuide = () => {
+      let guideInfo =
+        JSON.parse(localStorage.getItem('teacher-guideInfo')) || null;
+      if (!guideInfo) {
+        guideInfo = { teacherGuide: true };
+      } else {
+        guideInfo.teacherGuide = true;
+      }
+      localStorage.setItem('teacher-guideInfo', JSON.stringify(guideInfo));
+      tipShow.value = false;
+      //  localStorage.setItem('endC')
+    };
+    return () => (
+      <>
+        {tipShow.value ? (
+          <div
+            v-model:show={tipShow.value}
+            class={['n-modal-mask', 'n-modal-mask-guide']}>
+            <div class={styles.content} onClick={() => handleNext()}>
+              <div
+                class={styles.backBtn}
+                onClick={(e: Event) => {
+                  e.stopPropagation();
+                  endGuide();
+                }}>
+                跳过
+              </div>
+              <div
+                class={styles.box}
+                style={{...data.box,...data.steps[data.step].boxStyle}}
+                id={`modeType-${data.step}`}>
+
+                {data.steps.map((item: any, index) => (
+
+                  <div
+                    onClick={(e: Event) => e.stopPropagation()}
+                    class={styles.item}
+                    style={ item.type=='bottom'?  {
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                      top:`-${item.imgStyle?.height}`
+
+                    }:{
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                     top: `${data.box?.height}`,
+
+                    }}>
+                    <img
+                      class={styles.img}
+                      style={item.imgStyle}
+                      src={item.img}
+                    />
+                    {/* <img
+                      class={styles.iconHead}
+                      style={item.handStyle}
+                      src={getImage('indexDot.png')}
+                    /> */}
+                    <div class={styles.btns} style={item.btnsStyle}>
+                      {data.step + 1 == data.steps.length ? (
+                        <>
+                         <div
+                            class={[styles.endBtn]}
+                            onClick={() => endGuide()}>
+                            完成
+                          </div>
+                          <div
+                            class={styles.nextBtn}
+                            onClick={() => {
+                              data.step = 0;
+                              getStepELe();
+                            }}>
+                            再看一遍
+                          </div>
+
+                        </>
+                      ) : (
+                        <div class={styles.btn} onClick={() => handleNext()}>
+                          下一步 ({data.step + 1}/{data.steps.length})
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        ) : null}
+      </>
+    );
+  }
+});

+ 186 - 0
src/custom-plugins/guide-page/train-guide.tsx

@@ -0,0 +1,186 @@
+import { NButton } from 'naive-ui';
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import { getImage } from './images';
+import {px2vw,px2vwH} from '@/utils/index'
+export default defineComponent({
+  name: 'train-guide',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const data = reactive({
+      box: {
+        height:'0px',
+      } as any,
+      show: false,
+      /**
+       *
+            width:  px2vw(840),
+            height:  px2vw(295)
+       */
+      steps: [
+        {
+          ele: '',
+          eleRect: {} as DOMRect,
+          img: getImage('train2.png'),
+          handStyle: {
+            top: '0.91rem'
+          },
+          imgStyle: {
+            left: '-250px',
+            width:  '591px',
+            height:  '227px'
+          },
+          btnsStyle: {
+            bottom:'30px',
+            left: '-90px',
+          },
+          eleRectPadding:{
+            left:7,
+            top:7,
+            width:14,
+            height:14
+          },
+
+        },
+
+      ],
+      step: 0
+    });
+    const tipShow = ref(false);
+    const guideInfo = localStorage.getItem('teacher-guideInfo');
+    if (guideInfo && JSON.parse(guideInfo).trainGuide) {
+      tipShow.value = false;
+    } else {
+      tipShow.value = true;
+    }
+    const getStepELe = () => {
+
+      const ele: HTMLElement = document.getElementById(`train-${data.step}`)!;
+
+      if (ele) {
+        const eleRect = ele.getBoundingClientRect();
+        const left = data.steps[data.step].eleRectPadding?.left || 0;
+        const top = data.steps[data.step].eleRectPadding?.top || 0;
+        const width = data.steps[data.step].eleRectPadding?.width || 0;
+        const height = data.steps[data.step].eleRectPadding?.height || 0
+        data.box = {
+          left: eleRect.x - left+ 'px',
+          top: eleRect.y - top +'px',
+          width: eleRect.width + width+'px',
+          height: eleRect.height +height+ 'px'
+        };
+        console.log(`coai-${data.step}`,data.box);
+      }
+    };
+    onMounted(() => {
+      getStepELe();
+    });
+
+    const handleNext = () => {
+      if (data.step >= 4) {
+        endGuide();
+        return;
+      }
+      data.step = data.step + 1;
+      getStepELe();
+    };
+
+    const endGuide = () => {
+      let guideInfo =
+        JSON.parse(localStorage.getItem('teacher-guideInfo')) || null;
+      if (!guideInfo) {
+        guideInfo = { trainGuide: true };
+      } else {
+        guideInfo.trainGuide = true;
+      }
+      localStorage.setItem('teacher-guideInfo', JSON.stringify(guideInfo));
+      tipShow.value = false;
+      //  localStorage.setItem('endC')
+    };
+    return () => (
+      <>
+        {tipShow.value ? (
+          <div
+            v-model:show={tipShow.value}
+            class={['n-modal-mask', 'n-modal-mask-guide']}>
+            <div class={styles.content} onClick={() => handleNext()}>
+              <div
+                class={styles.backBtn}
+                onClick={(e: Event) => {
+                  e.stopPropagation();
+                  endGuide();
+                }}>
+                跳过
+              </div>
+              <div
+                class={styles.box}
+                style={{...data.box,...data.steps[data.step].boxStyle}}
+                id={`modeType-${data.step}`}>
+
+                {data.steps.map((item: any, index) => (
+
+                  <div
+                    onClick={(e: Event) => e.stopPropagation()}
+                    class={styles.item}
+                    style={ item.type=='bottom'?  {
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                      top:`-${item.imgStyle?.height}`
+
+                    }:{
+                      display: index === data.step ? '' : 'none',
+                      left: `${item.eleRect?.left}px`,
+                     top: `${data.box?.height}`,
+
+                    }}>
+                    <img
+                      class={styles.img}
+                      style={item.imgStyle}
+                      src={item.img}
+                    />
+                    {/* <img
+                      class={styles.iconHead}
+                      style={item.handStyle}
+                      src={getImage('indexDot.png')}
+                    /> */}
+                    <div class={styles.btns} style={item.btnsStyle}>
+                      {data.step + 1 == data.steps.length ? (
+                        <>
+                         <div
+                            class={[styles.endBtn]}
+                            onClick={() => endGuide()}>
+                            完成
+                          </div>
+                          <div
+                            class={styles.nextBtn}
+                            onClick={() => {
+                              data.step = 0;
+                              getStepELe();
+                            }}>
+                            再看一遍
+                          </div>
+
+                        </>
+                      ) : (
+                        <div class={styles.btn} onClick={() => handleNext()}>
+                          下一步 ({data.step + 1}/{data.steps.length})
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        ) : null}
+      </>
+    );
+  }
+});

+ 1 - 3
src/main.ts

@@ -6,13 +6,11 @@ import { setupNaive } from './plugins';
 import { setupStore } from './store';
 import 'dayjs/locale/zh-cn';
 import './styles/index.less';
-
+import './utils/rem';
 async function setupApp() {
   // app loading
   // const appLoading = createApp(AppLoading);
-
   // appLoading.mount('#appLoading');
-
   const app = createApp(App);
 
   setupNaive(app);

+ 3 - 0
src/utils/index.ts

@@ -322,3 +322,6 @@ export const getTimes = (
 };
 
 export const px2vw = (px: number): string => `${(px / 1920) * 100}vw`;
+
+export const px2vwH = (px: number): string =>
+  `${(((px / 1920) * 1920) / 1188) * 100}vw`;

+ 18 - 0
src/utils/rem.ts

@@ -0,0 +1,18 @@
+const baseSize = 16;
+// 设置 rem 函数
+function setRem() {
+  // 当前页面屏幕分辨率相对于1920宽的缩放比例,可根据自己需要修改
+  let scale = document.documentElement.clientWidth / 1920;
+  // 下面这一行代码可以忽略,这是我另外加的,我加这行代码是为了屏幕宽度小于1280时就不继续等比缩放了
+  if (document.documentElement.clientWidth < 1280) scale = 1280 / 1920;
+  // 设置页面根节点字体大小(“Math.min(scale, 2)” 指最高放大比例为2,可根据实际业务需求调整)
+  document.documentElement.style.fontSize = `${
+    baseSize * Math.min(scale, 1)
+  }px`;
+}
+// 初始化
+setRem();
+// 改变窗口大小时重新设置 rem
+window.onresize = () => {
+  setRem();
+};

+ 10 - 1
src/views/attend-class/index.tsx

@@ -34,7 +34,7 @@ import AudioPay from './component/audio-pay';
 import TrainSettings from './model/train-settings';
 import { useRoute } from 'vue-router';
 import { lessonPreTrainingPage, queryCourseware } from '../prepare-lessons/api';
-
+import Attentguide from '@/custom-plugins/guide-page/attent-guide'
 export type ToolType = 'init' | 'pen' | 'whiteboard';
 export type ToolItem = {
   type: ToolType;
@@ -100,6 +100,7 @@ export default defineComponent({
       timer: null as any,
       item: null as any
     });
+    const showGuide = ref(false)
     const getDetail = async () => {
       try {
         const res = await queryCourseware({
@@ -136,6 +137,9 @@ export default defineComponent({
             isRender: false // 是否渲染了
           };
         });
+        setTimeout(()=>{
+          showGuide.value = true;
+        },500)
       } catch {
         //
       }
@@ -611,6 +615,7 @@ export default defineComponent({
             <Transition name="right">
               {activeData.model && (
                 <div
+                id='attent-0'
                   class={styles.rightFixedBtns}
                   onClick={(e: Event) => {
                     e.stopPropagation();
@@ -666,6 +671,7 @@ export default defineComponent({
         </div> */}
         {/* 布置作业按钮 */}
         <div
+         id='attent-3'
           class={[
             styles.assignHomework,
             activeData.model ? '' : styles.sectionAnimateUp
@@ -702,6 +708,7 @@ export default defineComponent({
             {{
               trigger: () => (
                 <div
+                id='attent-1'
                   class={styles.displayBtn}
                   onClick={() =>
                     openStudyTool({
@@ -720,6 +727,7 @@ export default defineComponent({
             {{
               trigger: () => (
                 <div
+                id='attent-2'
                   class={styles.displayBtn}
                   onClick={() =>
                     openStudyTool({
@@ -828,6 +836,7 @@ export default defineComponent({
             <p>倒</p>
           </div>
         </NModal> */}
+        {showGuide.value?<Attentguide></Attentguide>:null}
       </div>
     );
   }

+ 82 - 35
src/views/classList/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, reactive, onMounted, ref } from 'vue';
+import { defineComponent, reactive, onMounted, ref, nextTick } from 'vue';
 import styles from './index.module.less';
 import {
   NButton,
@@ -20,13 +20,12 @@ import { classGroupList, deleteClass } from './api';
 import CreateClass from './modals/createClass';
 import RestStudentBox from './modals/restStudentBox';
 import TrainDetail from './modals/Gotoclass';
-import {
-  getgradeNumList,
-  classArray
-} from './contants';
+import { getgradeNumList, classArray } from './contants';
 import add from '@/views/studentList/images/add.png';
+import ClassGuide from '@/custom-plugins/guide-page/class-guide';
 import { useRouter } from 'vue-router';
 import { rowDark } from 'naive-ui/es/legacy-grid/styles';
+
 export default defineComponent({
   name: 'class-classList',
   setup(props, { emit }) {
@@ -60,8 +59,8 @@ export default defineComponent({
       getList();
       console.log('search', state);
     };
-
-    state.gradeNumList = getgradeNumList()
+    const showGuide = ref(false);
+    state.gradeNumList = getgradeNumList();
     const onReset = () => {
       state.searchForm = {
         keyword: null as any,
@@ -99,6 +98,12 @@ export default defineComponent({
         state.tableList = res.data.rows;
         state.pagination.pageTotal = res.data.total;
         state.loading = false;
+
+        setTimeout(() => {
+          if (state.tableList.length > 0) {
+            showGuide.value = true;
+          }
+        }, 500);
       } catch (e) {
         state.loading = false;
         console.log(e);
@@ -126,36 +131,77 @@ export default defineComponent({
         {
           title: '操作',
           key: 'id',
-          render(row: any) {
+          render(row: any, index: number) {
             return (
               <div>
                 <NSpace>
-                  <NButton
-                    type="primary"
-                    text
-                    onClick={() => {
-                      router.push({
-                        path: '/classDetail',
-                        query: { name: row.name, id: row.id }
-                      });
-                    }}>
-                    详情
-                  </NButton>
-                  <NButton
-                    type="primary"
-                    text
-                    onClick={() => {
-                      startResetStudent(row);
-                    }}>
-                    学生调整
-                  </NButton>
-                  <NButton
-                    disabled={!(row.preStudentNum > 0)}
-                    type="primary"
-                    text
-                    onClick={() => classesBegin(row)}>
-                    开始上课
-                  </NButton>
+                  {index == 0 ? (
+                    <div id="class-0">
+                      <NButton
+                        type="primary"
+                        text
+                        onClick={() => {
+                          router.push({
+                            path: '/classDetail',
+                            query: { name: row.name, id: row.id }
+                          });
+                        }}>
+                        详情
+                      </NButton>
+                    </div>
+                  ) : (
+                    <NButton
+                      type="primary"
+                      text
+                      onClick={() => {
+                        router.push({
+                          path: '/classDetail',
+                          query: { name: row.name, id: row.id }
+                        });
+                      }}>
+                      详情
+                    </NButton>
+                  )}
+
+                  {index == 0 ? (
+                    <NButton
+                      type="primary"
+                      {...{ id: 'class-1' }}
+                      text
+                      onClick={() => {
+                        startResetStudent(row);
+                      }}>
+                      学生调整
+                    </NButton>
+                  ) : (
+                    <NButton
+                      type="primary"
+                      text
+                      onClick={() => {
+                        startResetStudent(row);
+                      }}>
+                      学生调整
+                    </NButton>
+                  )}
+                  {index == 0 ? (
+                    <NButton
+                      {...{ id: 'class-2' }}
+                      disabled={!(row.preStudentNum > 0)}
+                      type="primary"
+                      text
+                      onClick={() => classesBegin(row)}>
+                      开始上课
+                    </NButton>
+                  ) : (
+                    <NButton
+                      disabled={!(row.preStudentNum > 0)}
+                      type="primary"
+                      text
+                      onClick={() => classesBegin(row)}>
+                      开始上课
+                    </NButton>
+                  )}
+
                   {!(row.preStudentNum > 0) ? (
                     <p
                       style={{ color: '#EA4132', cursor: 'pointer' }}
@@ -296,9 +342,10 @@ export default defineComponent({
           class={['modalTitle background', styles.chioseModel]}
           title={'选择课件'}>
           <TrainDetail
-          activeRow={state.activeRow}
+            activeRow={state.activeRow}
             onClose={() => (state.goCourseVisiable = false)}></TrainDetail>
         </NModal>
+        {showGuide.value ? <ClassGuide></ClassGuide> : null}
       </div>
     );
   }

+ 14 - 4
src/views/data-module/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, ref, watch,nextTick } from 'vue';
+import { defineComponent, ref, watch,nextTick, onMounted } from 'vue';
 import styles from './index.module.less';
 import { NTabs, NTabPane, NSpace, NButton } from 'naive-ui';
 import { useRoute } from 'vue-router';
@@ -7,6 +7,7 @@ import CDatePicker from '/src/components/CDatePicker';
 import TrainData from '@/views/home/components/trainData';
 import PracticeData from '@/views/home/components/practiceData';
 import PracticeRanking from '@/views/home/components/practiceRanking';
+import DataGuide from '@/custom-plugins/guide-page/data-guide'
 import {
   getNowDateAndMonday,
   getNowDateAndSunday
@@ -27,6 +28,7 @@ export default defineComponent({
     const setTabs = (val: any) => {
       setTabsCaches(val, 'tabName', route);
     };
+    const showGuide = ref(false)
     const timer = ref<[number, number]>([
       getNowDateAndMonday(new Date().getTime()),
       getNowDateAndSunday(new Date().getTime())
@@ -54,6 +56,13 @@ export default defineComponent({
       })
 
     }
+
+    onMounted(()=>{
+      setTimeout(()=>{
+        showGuide.value = true
+      },500)
+
+    })
     return () => (
       <div>
         <div class={styles.listWrap}>
@@ -91,20 +100,21 @@ export default defineComponent({
                 </>
               )
             }}>
-            <NTabPane name="training" tab="训练统计">
+            <NTabPane name="training" tab="训练统计"   v-slots={{ tab: () => <span id='data-0'>训练统计</span> }}>
               <TrainData ref={TrainDataRef} timer={timer.value}></TrainData>
             </NTabPane>
-            <NTabPane name="practice" tab="练习数据">
+            <NTabPane name="practice" tab="练习数据"  v-slots={{ tab: () => <span id='data-1'>练习数据</span> }}>
               <PracticeData
                 ref={PracticeDataRef}
                 timer={timer.value}></PracticeData>
             </NTabPane>
-            <NTabPane name="ranking" tab="练习排行">
+            <NTabPane name="ranking" tab="练习排行"  v-slots={{ tab: () => <span id='data-2'>练习排行</span> }}>
               <PracticeRanking
                 ref={PracticeRankingRef}
                 timer={timer.value}></PracticeRanking>
             </NTabPane>
           </NTabs>
+          {showGuide.value?<DataGuide></DataGuide>:null}
         </div>
       </div>
     );

+ 1 - 1
src/views/home/index.tsx

@@ -516,7 +516,7 @@ export default defineComponent({
               </div>
             </div>
             <img src={iconTo} class={styles.iconTo} />
-            <div class={styles.toolFunction}>
+            <div class={styles.toolFunction} id='home-4'>
               <div class={[styles.toolItem, styles.item1]}>
                 <img src={t1} />
                 <p class={styles.toolMemo}>提升效率,练习好节奏</p>

+ 8 - 4
src/views/natural-resources/components/my-collect/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, onMounted, reactive } from 'vue';
+import { defineComponent, onMounted, reactive, ref } from 'vue';
 import styles from './index.module.less';
 import CardType from '@/components/card-type';
 import Pagination from '@/components/pagination';
@@ -7,7 +7,7 @@ import { favorite, materialQueryPage, materialRemove } from '../../api';
 import { NSpin, useDialog, useMessage } from 'naive-ui';
 import TheEmpty from '@/components/TheEmpty';
 import CardPreview from '@/components/card-preview';
-
+import MyCollogeGuide from '@/custom-plugins/guide-page/myColloge-guide'
 export default defineComponent({
   name: 'share-resources',
   setup() {
@@ -58,11 +58,14 @@ export default defineComponent({
           });
         });
         state.tableList = temp || [];
+        setTimeout(()=>{
+          showGuide.value= true;
+        },500)
       } catch {
         state.loading = false;
       }
     };
-
+    const showGuide = ref(false)
     const onSearch = async (item: any) => {
       state.pagination.page = 1;
       state.searchGroup = Object.assign(state.searchGroup, item);
@@ -111,7 +114,7 @@ export default defineComponent({
         <SearchGroupResources onSearch={(item: any) => onSearch(item)} />
 
         <NSpin v-model:show={state.loading} style={{ 'min-height': '50vh' }}>
-          <div class={styles.list}>
+          <div class={styles.list} id='myColloge-0'>
             {state.tableList.map((item: any) => (
               <CardType
                 item={item}
@@ -142,6 +145,7 @@ export default defineComponent({
 
         {/* 弹窗查看 */}
         <CardPreview v-model:show={state.show} item={state.item} />
+        {showGuide.value?<MyCollogeGuide></MyCollogeGuide>:null}
       </>
     );
   }

+ 7 - 2
src/views/natural-resources/components/my-resources/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, onMounted, reactive } from 'vue';
+import { defineComponent, onMounted, reactive, ref } from 'vue';
 import styles from './index.module.less';
 import CardType from '/src/components/card-type';
 import Pagination from '/src/components/pagination';
@@ -15,7 +15,7 @@ import UploadModal from './upload-modal';
 import CardPreview from '@/components/card-preview';
 import resourceDefault from '../../images/resource-default.svg';
 import resourceChecked from '../../images/resource-checked.svg';
-
+import MyResourcesGuide from '@/custom-plugins/guide-page/myResources-guide'
 export default defineComponent({
   name: 'share-resources',
   setup() {
@@ -44,6 +44,7 @@ export default defineComponent({
       editList: [] as any, // TOD
       editIds: [] as any // 编辑的
     });
+    const showGuide = ref(false)
     const getList = async () => {
       try {
         state.loading = true;
@@ -70,6 +71,9 @@ export default defineComponent({
           });
         });
         state.tableList = temp || [];
+        setTimeout(()=>{
+          showGuide.value= true;
+        },500)
       } catch {
         state.loading = false;
       }
@@ -265,6 +269,7 @@ export default defineComponent({
             list={state.editList}
           />
         </NModal>
+        {showGuide.value?<MyResourcesGuide></MyResourcesGuide>:null}
       </>
     );
   }

+ 1 - 0
src/views/natural-resources/components/my-resources/search-group-resources.tsx

@@ -123,6 +123,7 @@ export default defineComponent({
                   type="primary"
                   class={styles.addTrain}
                   focusable={false}
+                  {...{ id: 'myResources-0' }}
                   strong
                   onClick={() => emit('upload')}>
                   <img src={iconUpload} class={styles.iconUpload} />

+ 9 - 3
src/views/natural-resources/components/share-resources/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, onMounted, reactive } from 'vue';
+import { defineComponent, onMounted, reactive, ref } from 'vue';
 import styles from './index.module.less';
 import CardType from '/src/components/card-type';
 import Pagination from '/src/components/pagination';
@@ -8,7 +8,7 @@ import { NModal, NSpin } from 'naive-ui';
 import TheEmpty from '/src/components/TheEmpty';
 import CardPreview from '/src/components/card-preview';
 import AddTeaching from '../../model/add-teaching';
-
+import ShareResourcesGuide from '@/custom-plugins/guide-page/shareResources-guide'
 export default defineComponent({
   name: 'share-resources',
   setup() {
@@ -32,6 +32,7 @@ export default defineComponent({
       show: false,
       item: {} as any
     });
+    const showGuide = ref(false)
     const getList = async () => {
       try {
         state.loading = true;
@@ -55,6 +56,9 @@ export default defineComponent({
           });
         });
         state.tableList = temp || [];
+        setTimeout(()=>{
+          showGuide.value = true
+        },500)
       } catch {
         state.loading = false;
       }
@@ -91,7 +95,7 @@ export default defineComponent({
         />
 
         <NSpin v-model:show={state.loading} style={{ 'min-height': '50vh' }}>
-          <div class={styles.list}>
+          <div class={styles.list} id='shareResources-1'>
             {state.tableList.map((item: any) => (
               <CardType
                 item={item}
@@ -131,6 +135,8 @@ export default defineComponent({
           blockScroll={false}>
           <AddTeaching onClose={() => (state.teachingStatus = false)} />
         </NModal>
+        {showGuide.value?<ShareResourcesGuide></ShareResourcesGuide>:null}
+
       </>
     );
   }

+ 5 - 3
src/views/natural-resources/index.tsx

@@ -4,6 +4,7 @@ import { NTabPane, NTabs } from 'naive-ui';
 import ShareResources from './components/share-resources';
 import MyResources from './components/my-resources';
 import MyCollect from './components/my-collect';
+
 export default defineComponent({
   name: 'student-studentList',
   setup() {
@@ -18,21 +19,22 @@ export default defineComponent({
           <NTabPane
             name="shareResources"
             tab="共享资源"
-            // displayDirective="show:lazy"
+            v-slots={{ tab: () => <span id='shareResources-0'>共享资源</span> }}
           >
             <ShareResources />
           </NTabPane>
           <NTabPane
             name="myResources"
             tab="我的资源"
-            // displayDirective="show:lazy"
+          // displayDirective="show:lazy"
+
           >
             <MyResources />
           </NTabPane>
           <NTabPane
             name="myCollect"
             tab="我的收藏"
-            // displayDirective="show:lazy"
+          // displayDirective="show:lazy"
           >
             <MyCollect />
           </NTabPane>

+ 1 - 0
src/views/prepare-lessons/components/directory-main/index.tsx

@@ -101,6 +101,7 @@ export default defineComponent({
       <div class={styles.directoryList}>
         {forms.showSelectBookStatus && (
           <div
+          id='lessons-0'
             class={styles['select-directory']}
             onClick={() => (forms.coursewareStatus = true)}>
             <span

+ 2 - 1
src/views/prepare-lessons/components/lesson-main/courseware/index.tsx

@@ -268,6 +268,7 @@ export default defineComponent({
               预览
             </NButton>
             <NButton
+            {...{ id: 'lessons-3' }}
               type="primary"
               onClick={() => {
                 let count = 0;
@@ -287,7 +288,7 @@ export default defineComponent({
           </NSpace>
         </div>
 
-        <NScrollbar class={styles.listContainer}>
+        <NScrollbar class={styles.listContainer}{...{id:'lessons-2'}} >
           <NSpin show={forms.loadingStatus}>
             <div
               class={[

+ 3 - 3
src/views/prepare-lessons/components/lesson-main/index.tsx

@@ -17,13 +17,13 @@ export default defineComponent({
           paneClass={styles.paneTitle}
           justifyContent="center"
           paneWrapperClass={styles.paneWrapperContainer}
-          onUpdate:value={(val: string) => {
+          onUpdate: value={(val: string) => {
             prepareStore.setTabType(val);
           }}>
-          <NTabPane name="courseware" tab="课件">
+          <NTabPane name="courseware" tab="课件" >
             <Courseware />
           </NTabPane>
-          <NTabPane name="train" tab="训练">
+          <NTabPane name="train" tab="训练" v-slots={{ tab: () => <span id='lessons-4'>训练</span> }} {...{ id: 'lessons-4' }}>
             <Train />
           </NTabPane>
         </NTabs>

+ 10 - 3
src/views/prepare-lessons/components/lesson-main/train/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, onMounted, reactive, watch } from 'vue';
+import { defineComponent, onMounted, reactive, watch ,ref} from 'vue';
 import styles from './index.module.less';
 import {
   NButton,
@@ -23,7 +23,7 @@ import {
 import { evaluateDifficult } from '/src/utils/contants';
 import TrainUpdate from '/src/views/attend-class/model/train-update';
 import AssignHomework from './assign-homework';
-
+import Trainguide from '@/custom-plugins/guide-page/train-guide'
 export default defineComponent({
   name: 'courseware-modal',
   setup() {
@@ -41,7 +41,7 @@ export default defineComponent({
       editStatus: false,
       editItem: {} as any
     });
-
+    const showGuide = ref(false)
     // 完成编辑
     const onOverEdit = async () => {
       dialog.warning({
@@ -109,6 +109,10 @@ export default defineComponent({
 
         forms.trainList = temp || [];
         prepareStore.setTrainList(temp || []);
+        setTimeout(()=>{
+          showGuide.value = true;
+        },500)
+
       } catch {
         //
       }
@@ -219,6 +223,7 @@ export default defineComponent({
           <NSpace>
             <NButton
               type="primary"
+              {...{id:'train-0'}}
               disabled={forms.drag}
               onClick={() => {
                 let count = 0;
@@ -344,6 +349,8 @@ export default defineComponent({
             onClose={() => (forms.assignHomeworkStatus = false)}
           />
         </NModal>
+        {showGuide.value?<Trainguide></Trainguide>:null}
+
       </div>
     );
   }

+ 13 - 4
src/views/prepare-lessons/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, onMounted, ref } from 'vue';
+import { defineComponent, onMounted, ref, computed } from 'vue';
 import styles from './index.module.less';
 import DirectoryList from './components/directory-main';
 import LessonMain from './components/lesson-main';
@@ -6,15 +6,23 @@ import ResourceMain from './components/resource-main';
 import { useResizeObserver } from '@vueuse/core';
 import { onBeforeRouteLeave } from 'vue-router';
 import { usePrepareStore } from '/src/store/modules/prepareLessons';
-
+import { storeToRefs } from 'pinia';
+import LessonsGuide from '@/custom-plugins/guide-page/lessons-guide'
 export default defineComponent({
   name: 'prepare-lessons',
   setup() {
     const prepareStore = usePrepareStore();
+
+    console.log(prepareStore, 'prepareStore')
+    const { treeList, coursewareList } = storeToRefs(prepareStore)
+    const showGuide = computed(() => {
+
+      return treeList.value.length > 0 && coursewareList.value.length > 0
+    })
     const directroyRef = ref();
     onMounted(() => {
       useResizeObserver(
-        document.querySelector('#resourceRef') as HTMLElement,
+        document.querySelector('#lessons-1') as HTMLElement,
         (entries: any) => {
           const entry = entries[0];
           const { height } = entry.contentRect;
@@ -57,9 +65,10 @@ export default defineComponent({
             //   ? styles.resourceClose
             //   : ''
           ]}
-          id="resourceRef">
+          id="lessons-1">
           <ResourceMain />
         </div>
+        {showGuide.value ? <LessonsGuide></LessonsGuide> : null}
       </div>
     );
   }

+ 8 - 3
src/views/setting/components/schoolInfo/index.tsx

@@ -30,7 +30,7 @@ import TheQrCode from '/src/components/TheQrCode';
 import logo from '@/common/images/logo.png';
 import { stringifyQuery } from '/src/router';
 import AddteacherModel from '../../modal/addteacherModel'
-
+import TeacherGuide from '@/custom-plugins/guide-page/teacher-guide'
 export default defineComponent({
   name: 'school-info',
   setup() {
@@ -54,7 +54,7 @@ export default defineComponent({
       modal: false,
       qrModal: false
     });
-
+    const showGuide = ref(false)
     const columns = (): DataTableColumn[] => {
       return [
         {
@@ -163,6 +163,9 @@ export default defineComponent({
       if (res?.code === 200 && Array.isArray(res?.data?.rows)) {
         data.dataList = res.data.rows;
       }
+      setTimeout(()=>{
+        showGuide.value = true;
+      },500)
     };
     onMounted(() => {
       getAreaList();
@@ -335,12 +338,13 @@ export default defineComponent({
 
         <NSpace style={{ padding: '32px 0' }}>
           <NButton
+          {...{id:'teacher-0'}}
             type="primary"
             renderIcon={() => <NIcon component={<Add />} />}
             onClick={() => (data.modal = true)}>
             添加老师
           </NButton>
-          <NButton type="primary" onClick={() => (data.qrModal = true)}>
+          <NButton    {...{id:'teacher-1'}} type="primary" onClick={() => (data.qrModal = true)}>
             老师注册二维码
           </NButton>
         </NSpace>
@@ -373,6 +377,7 @@ export default defineComponent({
               }}></AddteacherModel>
           </div>
         ) : null}
+        {showGuide.value?<TeacherGuide></TeacherGuide>:null}
       </div>
     );
   }

+ 18 - 5
src/views/studentList/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, onMounted, reactive } from 'vue';
+import { defineComponent, onMounted, reactive, ref } from 'vue';
 import styles from './index.module.less';
 import {
   NButton,
@@ -18,6 +18,7 @@ import { useRoute, useRouter } from 'vue-router';
 import { getStudentList } from './api';
 import { classGroupList } from '@/views/classList/api';
 import AddStudentModel from './modals/addStudentModel';
+import Studentguide from '@/custom-plugins/guide-page/student-guide'
 export default defineComponent({
   name: 'student-studentList',
   setup(props, { emit }) {
@@ -42,10 +43,12 @@ export default defineComponent({
       },
       tableList: [] as any,
       classList: [],
-      addStudentVisible: false
+      addStudentVisible: false,
+
     });
     const route = useRoute();
     const router = useRouter();
+    const showGuide = ref(false)
     const search = () => {
       state.pagination.page = 1;
       getList();
@@ -80,6 +83,9 @@ export default defineComponent({
         });
         state.tableList = res.data.rows;
         state.pagination.pageTotal = res.data.total;
+        if(state.tableList.length >0){
+          showGuide.value= true;
+        }
       } catch (e) {
         console.log(e);
       }
@@ -128,11 +134,16 @@ export default defineComponent({
         {
           title: '操作',
           key: 'id',
-          render(row: any) {
+          render(row: any,index:number) {
             return (
-              <NButton text type="primary" onClick={() => gotoDetail(row)}>
+              <>
+                {index==0? <NButton {...{id:'student-1'}} text type="primary" onClick={() => gotoDetail(row)}>
                 详情
-              </NButton>
+              </NButton>: <NButton text type="primary" onClick={() => gotoDetail(row)}>
+                详情
+              </NButton>}
+
+              </>
             );
           }
         }
@@ -235,6 +246,7 @@ export default defineComponent({
           </NForm>
         </div>
         <NButton
+        {...{id:'student-0'}}
           onClick={() => {
             state.addStudentVisible = true;
           }}
@@ -272,6 +284,7 @@ export default defineComponent({
               }}></AddStudentModel>
           </div>
         ) : null}
+          {showGuide.value?<Studentguide></Studentguide>:null}
       </div>
     );
   }

+ 60 - 18
src/views/xiaoku-music/index.tsx

@@ -39,7 +39,7 @@ import {
   api_subjectList
 } from '../xiaoku-ai/api';
 import { useUserStore } from '/src/store/modules/users';
-
+import Musicguide from '@/custom-plugins/guide-page/music-guide'
 export default defineComponent({
   name: 'XiaokuMusic',
   setup() {
@@ -64,6 +64,7 @@ export default defineComponent({
       playState: 'pause' as 'play' | 'pause',
       showPlayer: false
     });
+    const showGuide = ref(false)
     const getSubjects = async () => {
       const res = await api_subjectList();
       if (Array.isArray(res?.data)) {
@@ -94,6 +95,9 @@ export default defineComponent({
         getFavitor(data.list[data.listActive]);
       }
       data.loading = false;
+      setTimeout(() => {
+        showGuide.value = true
+      }, 500)
     };
 
     const handleGetList = () => {
@@ -212,22 +216,36 @@ export default defineComponent({
           <div class={styles.content}>
             <div class={styles.tools}>
               <NSpace
+
                 style={{ width: '100%' }}
                 size={[24, 12]}
                 wrapItem={false}>
-                {data.tags.map(item => (
-                  <NButton
-                    round
-                    textColor={data.tagIndex === item.id ? '#fff' : '#000'}
-                    color={data.tagIndex === item.id ? '#198CFE' : '#fff'}
-                    onClick={() => {
-                      data.tagIndex = item.id;
-                      data.reshing = true;
-                      handleGetList();
-                    }}>
-                    {item.name}
-                  </NButton>
-                ))}
+                <div  {...{
+                  id
+                    : 'music-0'
+                }}>
+                  <NSpace
+
+                    style={{ width: '100%' }}
+                    size={[24, 12]}
+                    wrapItem={false}>
+                    {data.tags.map(item => (
+
+                      <NButton
+                        round
+                        textColor={data.tagIndex === item.id ? '#fff' : '#000'}
+                        color={data.tagIndex === item.id ? '#198CFE' : '#fff'}
+                        onClick={() => {
+                          data.tagIndex = item.id;
+                          data.reshing = true;
+                          handleGetList();
+                        }}>
+                        {item.name}
+                      </NButton>
+
+                    ))}
+                  </NSpace>
+                </div>
               </NSpace>
               <TheSearch
                 style={{ marginLeft: 'auto' }}
@@ -265,7 +283,7 @@ export default defineComponent({
                             <PlayLoading
                               class={[
                                 data.listActive === index &&
-                                data.playState === 'play'
+                                  data.playState === 'play'
                                   ? ''
                                   : styles.showPlayLoading
                               ]}
@@ -277,9 +295,10 @@ export default defineComponent({
                             </div>
                             <div class={styles.titleDes}>{item.composer}</div>
                           </div>
-                          <NButton
+                          {index == 0 ? <NButton
                             color="#259CFE"
                             textColor="#fff"
+                            {...{ id: 'music-1' }}
                             round
                             class={styles.btn}
                             type="primary"
@@ -291,12 +310,32 @@ export default defineComponent({
                             <img
                               src={
                                 data.listActive === index &&
-                                data.playState === 'play'
+                                  data.playState === 'play'
                                   ? icon_pause
                                   : icon_play
                               }
                             />
-                          </NButton>
+                          </NButton> : <NButton
+                            color="#259CFE"
+                            textColor="#fff"
+                            round
+                            class={styles.btn}
+                            type="primary"
+                            onClick={(e: Event) => {
+                              e.stopPropagation();
+                              handlePlay(item);
+                            }}>
+                            试听
+                            <img
+                              src={
+                                data.listActive === index &&
+                                  data.playState === 'play'
+                                  ? icon_pause
+                                  : icon_play
+                              }
+                            />
+                          </NButton>}
+
                           <img class={styles.arrow} src={icon_arrow} />
                         </div>
                       </div>
@@ -320,6 +359,7 @@ export default defineComponent({
                   {activeItem.value.musicSheetName}
                 </div>
                 <img
+                  id='music-2'
                   style={{ display: activeItem.value.id ? '' : 'none' }}
                   class={styles.goBtn}
                   src={icon_goXiaoku}
@@ -365,6 +405,8 @@ export default defineComponent({
             onChange={value => handleChangeAudio(value)}
           />
         )}
+        {showGuide.value ? <Musicguide ></Musicguide> : null}
+
       </div>
     );
   }

+ 8 - 0
vite.config.ts

@@ -6,6 +6,14 @@ import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
 import viteESLint from 'vite-plugin-eslint';
 import legacyPlugin from '@vitejs/plugin-legacy';
 import { VitePWA } from 'vite-plugin-pwa';
+// 引入等比适配插件
+import px2rem from 'postcss-px2rem';
+
+// 配置基本大小
+const postcss = px2rem({
+  // 基准大小 baseSize,需要和rem.js中相同
+  remUnit: 16
+});
 // eslint-disable-next-line @typescript-eslint/no-var-requires
 const path = require('path');