Browse Source

第一版

xiaoxin 2 years ago
parent
commit
423b269e3c
79 changed files with 9373 additions and 1 deletions
  1. 30 0
      .gitignore
  2. 3 0
      .vscode/extensions.json
  3. 40 1
      README.md
  4. 3 0
      env.d.ts
  5. 13 0
      index.html
  6. 3291 0
      package-lock.json
  7. 31 0
      package.json
  8. BIN
      public/favicon.ico
  9. 56 0
      src/App.vue
  10. BIN
      src/assets/font/庞门正道标题体.ttf
  11. BIN
      src/assets/images/bg.png
  12. BIN
      src/assets/images/box-bg.png
  13. BIN
      src/assets/images/box-bg2.png
  14. BIN
      src/assets/images/box-bg3.png
  15. BIN
      src/assets/images/box-bg4.png
  16. BIN
      src/assets/images/box-bg5.png
  17. BIN
      src/assets/images/box-bg6.png
  18. BIN
      src/assets/images/box-icon.png
  19. BIN
      src/assets/images/building.png
  20. BIN
      src/assets/images/building2.png
  21. BIN
      src/assets/images/building3.png
  22. BIN
      src/assets/images/building4.png
  23. BIN
      src/assets/images/class-bg.png
  24. BIN
      src/assets/images/device-icon.png
  25. BIN
      src/assets/images/device-icon2.png
  26. BIN
      src/assets/images/device.png
  27. BIN
      src/assets/images/energy-box-bg.png
  28. BIN
      src/assets/images/energy-icon-active.png
  29. BIN
      src/assets/images/energy-icon.png
  30. BIN
      src/assets/images/energy-left-icon.png
  31. BIN
      src/assets/images/energy-left-icon2.png
  32. BIN
      src/assets/images/evenly-box-icon.png
  33. BIN
      src/assets/images/evenly-box-icon2.png
  34. BIN
      src/assets/images/header-bg.png
  35. BIN
      src/assets/images/icon-bg.png
  36. BIN
      src/assets/images/line-bg.png
  37. BIN
      src/assets/images/more-title-bg.png
  38. BIN
      src/assets/images/pop-title-bg.png
  39. BIN
      src/assets/images/school-icon-active.png
  40. BIN
      src/assets/images/school-icon.png
  41. BIN
      src/assets/images/student-title-bg.png
  42. BIN
      src/assets/images/teacher-title-bg.png
  43. BIN
      src/assets/images/teacher_icon.png
  44. BIN
      src/assets/images/teacher_icon2.png
  45. BIN
      src/assets/images/title-bg.png
  46. BIN
      src/assets/images/title-bg2.png
  47. BIN
      src/assets/images/user-icon-active.png
  48. BIN
      src/assets/images/user-icon.png
  49. 1 0
      src/assets/logo.svg
  50. 37 0
      src/assets/main.css
  51. 62 0
      src/components/building.vue
  52. 102 0
      src/components/control.vue
  53. 421 0
      src/components/energy/energyLeft.vue
  54. 366 0
      src/components/energy/energyRight.vue
  55. 95 0
      src/components/header.vue
  56. 386 0
      src/components/school/schoolLeft.vue
  57. 345 0
      src/components/school/schoolRight.vue
  58. 412 0
      src/components/student/studentCenter.vue
  59. 378 0
      src/components/student/studentLeft.vue
  60. 288 0
      src/components/student/studentRight.vue
  61. 377 0
      src/components/teacher/teacherCenter.vue
  62. 412 0
      src/components/teacher/teacherLeft.vue
  63. 445 0
      src/components/teacher/teacherRight.vue
  64. 629 0
      src/components/user/userLeft.vue
  65. 568 0
      src/components/user/userRight.vue
  66. 27 0
      src/main.ts
  67. 45 0
      src/router/index.ts
  68. 11 0
      src/utils/countUpNum.js
  69. 30 0
      src/utils/getTime.ts
  70. 24 0
      src/views/energy.vue
  71. 196 0
      src/views/more.vue
  72. 24 0
      src/views/school.vue
  73. 72 0
      src/views/student.vue
  74. 72 0
      src/views/teacher.vue
  75. 24 0
      src/views/user.vue
  76. 13 0
      tsconfig.app.json
  77. 11 0
      tsconfig.json
  78. 17 0
      tsconfig.node.json
  79. 16 0
      vite.config.ts

+ 30 - 0
.gitignore

@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
+}

+ 40 - 1
README.md

@@ -1 +1,40 @@
-#wanzai_lingdaojiashi
+# wanzai
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
+
+## Type Support for `.vue` Imports in TS
+
+TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
+
+If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
+
+1. Disable the built-in TypeScript Extension
+    1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
+    2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
+2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vitejs.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Type-Check, Compile and Minify for Production
+
+```sh
+npm run build
+```

+ 3 - 0
env.d.ts

@@ -0,0 +1,3 @@
+/// <reference types="vite/client" />
+declare module'element-plus/dist/locale/zh-cn.mjs'
+declare module'@/utils/countUpNum'

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="icon" href="/favicon.ico">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Vite App</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

File diff suppressed because it is too large
+ 3291 - 0
package-lock.json


+ 31 - 0
package.json

@@ -0,0 +1,31 @@
+{
+  "name": "wanzai",
+  "version": "0.0.0",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "run-p type-check \"build-only {@}\" --",
+    "preview": "vite preview",
+    "build-only": "vite build",
+    "type-check": "vue-tsc --build --force"
+  },
+  "dependencies": {
+    "countup.js": "^2.8.0",
+    "echarts": "^5.4.3",
+    "element-plus": "^2.4.4",
+    "sass": "^1.69.6",
+    "vue": "^3.3.11",
+    "vue-router": "^4.2.5"
+  },
+  "devDependencies": {
+    "@tsconfig/node18": "^18.2.2",
+    "@types/node": "^18.19.3",
+    "@vitejs/plugin-vue": "^4.5.2",
+    "@vue/tsconfig": "^0.5.0",
+    "npm-run-all2": "^6.1.1",
+    "typescript": "~5.3.0",
+    "vite": "^5.0.10",
+    "vue-tsc": "^1.8.25"
+  }
+}

BIN
public/favicon.ico


+ 56 - 0
src/App.vue

@@ -0,0 +1,56 @@
+<template>
+  <div class="container">
+    <!-- 遮罩层 -->
+    <div class="mask"></div>
+
+    <!-- 头部区域 -->
+    <Header />
+
+    <!-- 主内容区域 -->
+    <RouterView />
+
+    <!-- 路由切换区域 -->
+    <Control v-if="isShow" />
+
+    <!-- 楼栋图标区域 -->
+    <Building />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from "vue";
+import { useRoute } from "vue-router";
+import Header from "@/components/header.vue";
+import Control from "@/components/control.vue";
+import Building from "@/components/building.vue";
+import { RouterView } from "vue-router";
+
+const route = useRoute();
+
+// 是否展示切换路由组件
+const isShow = ref(false);
+
+// 监听路由地址变化
+watch(route, (value) => {
+  isShow.value = value.meta.showRouter as boolean;
+});
+</script>
+
+<style lang="scss" scoped>
+.container {
+  position: relative;
+  width: 1920px;
+  height: 1080px;
+  background-image: url(@/assets/images/bg.png);
+  background-size: 100% 100%;
+
+  .mask {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 267px;
+    background-image: linear-gradient(#010f0fff, #0b2026c6, transparent);
+  }
+}
+</style>

BIN
src/assets/font/庞门正道标题体.ttf


BIN
src/assets/images/bg.png


BIN
src/assets/images/box-bg.png


BIN
src/assets/images/box-bg2.png


BIN
src/assets/images/box-bg3.png


BIN
src/assets/images/box-bg4.png


BIN
src/assets/images/box-bg5.png


BIN
src/assets/images/box-bg6.png


BIN
src/assets/images/box-icon.png


BIN
src/assets/images/building.png


BIN
src/assets/images/building2.png


BIN
src/assets/images/building3.png


BIN
src/assets/images/building4.png


BIN
src/assets/images/class-bg.png


BIN
src/assets/images/device-icon.png


BIN
src/assets/images/device-icon2.png


BIN
src/assets/images/device.png


BIN
src/assets/images/energy-box-bg.png


BIN
src/assets/images/energy-icon-active.png


BIN
src/assets/images/energy-icon.png


BIN
src/assets/images/energy-left-icon.png


BIN
src/assets/images/energy-left-icon2.png


BIN
src/assets/images/evenly-box-icon.png


BIN
src/assets/images/evenly-box-icon2.png


BIN
src/assets/images/header-bg.png


BIN
src/assets/images/icon-bg.png


BIN
src/assets/images/line-bg.png


BIN
src/assets/images/more-title-bg.png


BIN
src/assets/images/pop-title-bg.png


BIN
src/assets/images/school-icon-active.png


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


BIN
src/assets/images/student-title-bg.png


BIN
src/assets/images/teacher-title-bg.png


BIN
src/assets/images/teacher_icon.png


BIN
src/assets/images/teacher_icon2.png


BIN
src/assets/images/title-bg.png


BIN
src/assets/images/title-bg2.png


BIN
src/assets/images/user-icon-active.png


BIN
src/assets/images/user-icon.png


+ 1 - 0
src/assets/logo.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

+ 37 - 0
src/assets/main.css

@@ -0,0 +1,37 @@
+*{
+    padding: 0;
+    margin: 0;
+    box-sizing: border-box;
+    text-decoration: none;
+}
+
+
+@keyframes float {
+    50%{
+        transform: translateY(-10px);
+    }
+    100%{
+        transform: translateY(0);
+    }
+}
+
+@keyframes float2 {
+    50%{
+        transform: translateY(-5px);
+    }
+    100%{
+        transform: translateY(0);
+    }
+}
+
+@keyframes run {
+    100%{
+        transform: translateY(-100%);
+    }
+}
+
+/* 引入字体 */
+@font-face {
+    font-family: '庞门正道标题体';
+    src: url(@/assets/font/庞门正道标题体.ttf);
+}

+ 62 - 0
src/components/building.vue

@@ -0,0 +1,62 @@
+<template>
+  <!-- 探理楼 -->
+  <div class="box icon" @click="test"></div>
+  <!-- 探问楼 -->
+  <div class="box icon2" @click="test2"></div>
+  <!-- 探学楼 -->
+  <div class="box icon3" @click="test3"></div>
+  <!-- 探真楼 -->
+  <div class="box icon4" @click="test4"></div>
+</template>
+
+<script setup lang="ts">
+const test = () => {
+  console.log("楼栋1");
+};
+
+const test2 = () => {
+  console.log("楼栋2");
+};
+
+const test3 = () => {
+  console.log("楼栋3");
+};
+
+const test4 = () => {
+  console.log("楼栋4");
+};
+</script>
+
+<style scoped>
+.box {
+  position: absolute;
+  width: 170px;
+  height: 130px;
+  background-size: 100% 100%;
+  cursor: pointer;
+  animation: float2 3s linear infinite;
+}
+.icon {
+  top: 512px;
+  left: 605px;
+  background-image: url(@/assets/images/building.png);
+}
+
+.icon2 {
+  top: 415px;
+  left: 784px;
+  background-image: url(@/assets/images/building2.png);
+}
+
+.icon3 {
+  top: 468px;
+  left: 1090px;
+  background-image: url(@/assets/images/building3.png);
+}
+
+.icon4 {
+  top: 324px;
+  left: 1051px;
+  background-image: url(@/assets/images/building4.png);
+}
+</style>

+ 102 - 0
src/components/control.vue

@@ -0,0 +1,102 @@
+<template>
+  <div class="control">
+    <router-link to="/" class="control_box" active-class="active">
+      <img
+        v-if="currentPath === 'school'"
+        class="icon"
+        src="@/assets/images/school-icon-active.png"
+      />
+      <img v-else class="icon" src="@/assets/images/school-icon.png" />
+      <div class="text">校园概览</div>
+      <div class="subText">campus overview</div>
+    </router-link>
+
+    <router-link to="energy" class="control_box" active-class="active">
+      <img
+        v-if="currentPath === 'energy'"
+        class="icon"
+        src="@/assets/images/energy-icon-active.png"
+      />
+      <img v-else class="icon" src="@/assets/images/energy-icon.png" />
+      <div class="text">能耗管理</div>
+      <div class="subText">energy management</div>
+    </router-link>
+
+    <router-link to="user" class="control_box" active-class="active">
+      <img
+        v-if="currentPath === 'user'"
+        class="icon"
+        src="@/assets/images/user-icon-active.png"
+      />
+      <img v-else class="icon" src="@/assets/images/user-icon.png" />
+      <div class="text">用户画像</div>
+      <div class="subText">user portrait</div>
+    </router-link>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { watch, ref } from "vue";
+import { RouterLink, useRoute } from "vue-router";
+
+// 当前路由信息
+const route = useRoute();
+// 当前路由路径
+const currentPath = ref("");
+
+// 监听路由路径变化
+watch(
+  route,
+  (value) => {
+    currentPath.value = value.name as string;
+  },
+  { immediate: true }
+);
+</script>
+
+<style lang="scss" scoped>
+.control {
+  position: absolute;
+  left: 50%;
+  bottom: 0;
+  transform: translate(-50%);
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  width: 780px;
+  height: 200px;
+  font-family: "庞门正道标题体";
+
+  .control_box {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    align-items: center;
+    height: 162px;
+    background-image: linear-gradient(#ffffff, #007aa3);
+    background-clip: text;
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+
+    .icon {
+      width: 91px;
+      height: 97px;
+    }
+
+    .text {
+      font-size: 28px;
+    }
+
+    .subText {
+      font-size: 16px;
+    }
+  }
+
+  .active {
+    background-image: linear-gradient(#ffe9a8, #de7b02);
+    background-clip: text;
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+  }
+}
+</style>

+ 421 - 0
src/components/energy/energyLeft.vue

@@ -0,0 +1,421 @@
+<template>
+  <div class="container">
+    <!-- 标题区域 -->
+    <div class="title">
+      <div class="title_text">能耗管理</div>
+      <div class="title_sub">Energy Management</div>
+    </div>
+
+    <!-- 内容区域 -->
+    <div class="content">
+      <div class="sub_title">能耗统计</div>
+      <!-- 用电量总计区域 -->
+      <div class="energy_box">
+        <div class="energy_box_left">
+          <div class="left_top">
+            <img src="@/assets/images/energy-left-icon.png" />
+            用电量总计(kw.h)
+          </div>
+          <div class="left_bottom" ref="eleValueDom">{{ eleValue }}</div>
+        </div>
+        <div class="energy_box_right">
+          <div class="right_top">增速</div>
+          <div class="right_bottom">
+            <div ref="eleSpeedDom">{{ eleSpeed }}</div>
+            <span>%</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 用水量总计区域 -->
+      <div class="energy_box">
+        <div class="energy_box_left">
+          <div class="left_top">
+            <img src="@/assets/images/energy-left-icon2.png" />
+            用水量总计(m³)
+          </div>
+          <div class="left_bottom" ref="waterValueDom">{{ waterValue }}</div>
+        </div>
+        <div class="energy_box_right">
+          <div class="right_top">增速</div>
+          <div class="right_bottom">
+            <div ref="waterSpeedDom">{{ waterSpeed }}</div>
+            <span>%</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 平均用量区域 -->
+      <div class="evenly">
+        <div class="evenly_box">
+          <img src="@/assets/images/evenly-box-icon.png" />
+          <div class="box_right">
+            <div class="right_top">
+              <div ref="eleValueAvDom">{{ eleValueAv }}</div>
+              <span>kw.h</span>
+            </div>
+            <div>人均用电量</div>
+          </div>
+        </div>
+
+        <div class="evenly_box">
+          <img src="@/assets/images/evenly-box-icon2.png" />
+          <div class="box_right">
+            <div class="right_top">
+              <div ref="waterValueAvDom">{{ waterValueAv }}</div>
+              <span>m³</span>
+            </div>
+            <div>人均用水量</div>
+          </div>
+        </div>
+      </div>
+
+      <div class="sub_title">区域能耗统计</div>
+
+      <!-- 水电切换按钮区域 -->
+      <div class="change">
+        <div
+          class="button"
+          :class="currentIndex === '水' ? 'active' : ''"
+          @click="handleClickWater"
+        >
+          水
+        </div>
+        <div
+          class="button"
+          :class="currentIndex === '电' ? 'active' : ''"
+          @click="handleClickEle"
+        >
+          电
+        </div>
+      </div>
+
+      <!-- 区域能耗统计图表 -->
+      <div class="bar" ref="barChart"></div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import * as Echarts from "echarts";
+import { countUpNum } from "@/utils/countUpNum";
+
+const eleValue = ref(888.88);
+const eleSpeed = ref(10.06);
+const waterValue = ref(88.88);
+const waterSpeed = ref(10.06);
+const eleValueAv = ref(88.88);
+const waterValueAv = ref(38.88);
+
+const eleValueDom = ref();
+const eleSpeedDom = ref();
+const waterValueDom = ref();
+const waterSpeedDom = ref();
+const eleValueAvDom = ref();
+const waterValueAvDom = ref();
+
+// 区域能耗统计图表实例
+let myBarChart: any;
+// 区域能耗统计图表DOM
+const barChart = ref(null);
+
+// 默认选中按钮
+const currentIndex = ref("水");
+
+onMounted(() => {
+  myBarChart = Echarts.init(barChart.value);
+  initBarChart();
+
+  getCountUpNum();
+});
+
+// 点击水 按钮回调
+const handleClickWater = () => {
+  if (currentIndex.value !== "水") {
+    currentIndex.value = "水";
+    initBarChart();
+  }
+};
+
+// 点击电 按钮回调
+const handleClickEle = () => {
+  if (currentIndex.value !== "电") {
+    currentIndex.value = "电";
+    initBarChart();
+  }
+};
+
+// 初始化区域能耗统计
+const initBarChart = () => {
+  const options = {
+    tooltip: {
+      trigger: "axis",
+      axisPointer: {
+        type: "shadow",
+      },
+    },
+    legend: {
+      data: ["教学楼", "行政楼", "报告厅", "食堂"],
+      top: 45,
+      right: 22,
+      textStyle: {
+        color: "#fff",
+      },
+    },
+    color: ["#0A87E8", "#BFA23B", "#09B9DB", "#00BAAD"],
+    grid: {
+      top: "28%",
+      left: "3%",
+      right: "6%",
+      bottom: "6%",
+      containLabel: true,
+    },
+    xAxis: [
+      {
+        type: "category",
+        axisTick: { show: false },
+        axisLabel: {
+          color: "#fff",
+        },
+        data: ["7月", "8月", "9月", "10月", "11月", "12月"],
+      },
+    ],
+    yAxis: [
+      {
+        type: "value",
+        name: currentIndex.value === "水" ? "m³" : "kw.h",
+        nameTextStyle: {
+          fontSize: 14,
+          color: "#fff",
+        },
+        axisLabel: {
+          color: "#fff",
+        },
+        splitLine: {
+          show: false,
+        },
+      },
+    ],
+    series: [
+      {
+        name: "教学楼",
+        type: "bar",
+        barGap: 0,
+        emphasis: {
+          focus: "series",
+        },
+        data: [320, 332, 301, 334, 390, 360],
+      },
+      {
+        name: "行政楼",
+        type: "bar",
+
+        emphasis: {
+          focus: "series",
+        },
+        data: [220, 182, 191, 234, 290, 390],
+      },
+      {
+        name: "报告厅",
+        type: "bar",
+
+        emphasis: {
+          focus: "series",
+        },
+        data: [150, 232, 201, 154, 190, 370],
+      },
+      {
+        name: "食堂",
+        type: "bar",
+        emphasis: {
+          focus: "series",
+        },
+        data: [98, 77, 101, 99, 40, 390],
+      },
+    ],
+  };
+
+  myBarChart.setOption(options);
+};
+
+// 让数字跳动
+const getCountUpNum = () => {
+  countUpNum(eleValueDom.value, eleValue.value, 2);
+  countUpNum(waterValueDom.value, waterValue.value, 2);
+  countUpNum(eleSpeedDom.value, eleSpeed.value, 2);
+  countUpNum(waterSpeedDom.value, waterSpeed.value, 2);
+  countUpNum(eleValueAvDom.value, eleValueAv.value, 2);
+  countUpNum(waterValueAvDom.value, waterValueAv.value, 2);
+};
+</script>
+
+<style lang="scss" scoped>
+.container {
+  width: 435px;
+  height: 951px;
+  color: #fff;
+  background-image: url(@/assets/images/box-bg.png);
+
+  .title {
+    display: flex;
+    align-items: center;
+    width: 430px;
+    height: 47px;
+    font-family: "庞门正道标题体";
+    background-image: url(@/assets/images/title-bg.png);
+    background-size: 100% 100%;
+
+    .title_text {
+      margin-left: 38px;
+      font-size: 20px;
+      text-shadow: 0px 0px 9px #158eff;
+    }
+
+    .title_sub {
+      margin-left: 19px;
+      font-size: 12px;
+      color: #215a8e;
+    }
+  }
+
+  .content {
+    padding: 20px 19px 0 14px;
+    height: 904px;
+
+    .sub_title {
+      padding-left: 28px;
+      height: 35px;
+      line-height: 18px;
+      color: #abd6ff;
+      font-weight: bold;
+      background-image: url(@/assets/images/title-bg2.png);
+      background-size: 100% 100%;
+    }
+
+    .energy_box {
+      display: flex;
+      justify-content: space-between;
+      margin-top: 24px;
+      padding: 0 30px 0 26px;
+      width: 100%;
+      height: 88px;
+      background-image: url(@/assets/images/energy-box-bg.png);
+      background-size: 100% 100%;
+
+      .energy_box_left {
+        .left_top {
+          display: flex;
+          align-items: center;
+          margin-top: 9px;
+          font-size: 16px;
+
+          img {
+            margin-right: 4px;
+            width: 20px;
+            height: 20px;
+          }
+        }
+
+        .left_bottom {
+          margin-top: 2px;
+          font-size: 38px;
+          font-weight: bold;
+        }
+      }
+
+      .energy_box_right {
+        min-width: 100px;
+        .right_top {
+          margin-top: 22px;
+          font-size: 14px;
+        }
+
+        .right_bottom {
+          display: flex;
+          align-items: flex-end;
+          margin-top: 2px;
+          color: #ffd15c;
+          font-size: 24px;
+          font-weight: bold;
+          span {
+            margin-left: 2px;
+            font-size: 18px;
+          }
+        }
+      }
+    }
+
+    .evenly {
+      display: flex;
+      height: 130px;
+
+      .evenly_box {
+        flex: 1;
+        display: flex;
+        align-items: center;
+
+        img {
+          margin-right: 8px;
+          width: 84px;
+          height: 48px;
+        }
+
+        .box_right {
+          font-size: 16px;
+
+          .right_top {
+            display: flex;
+            align-items: center;
+            font-weight: bold;
+            font-size: 24px;
+
+            span {
+              margin-top: 5px;
+              margin-left: 5px;
+              font-size: 12px;
+              font-weight: 400;
+            }
+          }
+        }
+      }
+    }
+
+    .change {
+      display: flex;
+      justify-content: flex-end;
+      align-items: flex-end;
+      height: 60px;
+      .button {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        margin-left: 18px;
+        width: 88px;
+        height: 32px;
+        font-size: 14px;
+        border-radius: 4px;
+        border: 1px solid #bef4f7;
+        background-image: linear-gradient(
+          rgba(156, 255, 248, 0.4),
+          rgba(152, 217, 237, 0.15),
+          rgba(188, 216, 247, 0.4)
+        );
+        cursor: pointer;
+      }
+
+      .active {
+        background-image: linear-gradient(
+          rgba(29, 242, 228, 0.8),
+          rgba(61, 198, 239, 0.4),
+          rgba(26, 94, 232, 0.8)
+        );
+      }
+    }
+
+    .bar {
+      height: 398px;
+    }
+  }
+}
+</style>

+ 366 - 0
src/components/energy/energyRight.vue

@@ -0,0 +1,366 @@
+<template>
+  <div class="container">
+    <!-- 标题区域 -->
+    <div class="title">
+      <div class="title_text">设备总览</div>
+      <div class="title_sub">Anlageuebersicht</div>
+    </div>
+
+    <!-- 内容区域 -->
+    <div class="content">
+      <div class="sub_title">设备情况</div>
+      <div class="device">
+        <!-- 水表情况 -->
+        <div class="device_box">
+          <div class="box_msg">
+            <div class="msg_num">
+              <span ref="waterValueDom">
+                {{ waterValue }}
+              </span>
+              个
+            </div>
+            <div class="msg_icon">
+              <img src="@/assets/images/device-icon.png" />
+            </div>
+            <div class="msg_name">水表</div>
+          </div>
+          <div class="box_content">
+            <div>在线水表</div>
+            <div>
+              <span class="content_num" ref="waterOnDom">{{ waterOn }}</span>
+              个
+            </div>
+          </div>
+          <div class="box_content">
+            <div>离线水表</div>
+            <div>
+              <span class="content_num" ref="waterOffDom">{{ waterOff }}</span>
+              个
+            </div>
+          </div>
+        </div>
+
+        <!-- 电表情况 -->
+        <div class="device_box">
+          <div class="box_msg">
+            <div class="msg_num">
+              <span ref="eleValueDom">{{ eleValue }}</span
+              >个
+            </div>
+            <div class="msg_icon">
+              <img src="@/assets/images/device-icon.png" />
+            </div>
+            <div class="msg_name">电表</div>
+          </div>
+          <div class="box_content">
+            <div>在线电表</div>
+            <div>
+              <span class="content_num" ref="eleOnDom">{{ eleOn }}</span>
+              个
+            </div>
+          </div>
+          <div class="box_content">
+            <div>离线电表</div>
+            <div>
+              <span class="content_num" ref="eleOffDom">{{ eleOff }}</span>
+              个
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="sub_title">用水量实时监测</div>
+
+      <div class="form_water form">
+        <el-table :data="tableData">
+          <el-table-column
+            prop="building"
+            label="楼栋"
+            align="center"
+            width="110"
+            show-overflow-tooltip
+          />
+          <el-table-column prop="num" label="编号" align="center" width="50" />
+          <el-table-column
+            prop="value"
+            label="用水量(m³)"
+            align="center"
+            width="110"
+          />
+          <el-table-column
+            prop="time"
+            label="通讯时间"
+            align="center"
+            width="132"
+          />
+        </el-table>
+      </div>
+
+      <div class="sub_title">用电量实时监测</div>
+
+      <div class="form_ele form">
+        <el-table :data="tableData">
+          <el-table-column
+            prop="building"
+            label="楼栋"
+            align="center"
+            width="110"
+            show-overflow-tooltip
+          />
+          <el-table-column prop="num" label="编号" align="center" width="50" />
+          <el-table-column
+            prop="value"
+            label="用电量(kw.h)"
+            align="center"
+            width="110"
+          />
+          <el-table-column
+            prop="time"
+            label="通讯时间"
+            align="center"
+            width="132"
+          />
+        </el-table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import { countUpNum } from "@/utils/countUpNum";
+
+const waterValue = ref(2345);
+const waterOn = ref(1000);
+const waterOff = ref(100);
+const eleValue = ref(2345);
+const eleOn = ref(1000);
+const eleOff = ref(100);
+
+const waterValueDom = ref(2345);
+const waterOnDom = ref(1000);
+const waterOffDom = ref(100);
+const eleValueDom = ref(2345);
+const eleOnDom = ref(1000);
+const eleOffDom = ref(100);
+
+const tableData = [
+  {
+    building: "教学楼 A",
+    num: "123",
+    value: "1233.00",
+    time: "2023-11-29 15:49:12",
+  },
+  {
+    building: "教学楼(A)",
+    num: "123",
+    value: "1233.00",
+    time: "2023-11-29 15:49:12",
+  },
+  {
+    building: "教学楼(A)",
+    num: "123",
+    value: "1233.00",
+    time: "2023-11-29 15:49:12",
+  },
+  {
+    building: "教学楼(A)",
+    num: "123",
+    value: "1233.00",
+    time: "2023-11-29 15:49:12",
+  },
+  {
+    building: "教学楼(A)",
+    num: "123",
+    value: "1233.00",
+    time: "2023-11-29 15:49:12",
+  },
+  {
+    building: "教学楼(A)",
+    num: "123",
+    value: "1233.00",
+    time: "2023-11-29 15:49:12",
+  },
+];
+
+onMounted(() => {
+  getCountUpNum();
+});
+
+const getCountUpNum = () => {
+  countUpNum(waterValueDom.value, waterValue.value);
+  countUpNum(waterOnDom.value, waterOn.value);
+  countUpNum(waterOffDom.value, waterOff.value);
+  countUpNum(eleValueDom.value, eleValue.value);
+  countUpNum(eleOnDom.value, eleOn.value);
+  countUpNum(eleOffDom.value, eleOff.value);
+};
+</script>
+
+<style lang="scss" scoped>
+.container {
+  width: 435px;
+  height: 951px;
+  color: #fff;
+  background-image: url(@/assets/images/box-bg.png);
+
+  .title {
+    display: flex;
+    align-items: center;
+    width: 430px;
+    height: 47px;
+    font-family: "庞门正道标题体";
+    background-image: url(@/assets/images/title-bg.png);
+    background-size: 100% 100%;
+
+    .title_text {
+      margin-left: 38px;
+      font-size: 20px;
+      text-shadow: 0px 0px 9px #158eff;
+    }
+
+    .title_sub {
+      margin-left: 19px;
+      font-size: 12px;
+      color: #215a8e;
+    }
+  }
+
+  .content {
+    padding: 20px 19px 0 14px;
+    height: 904px;
+
+    .sub_title {
+      padding-left: 28px;
+      height: 35px;
+      line-height: 18px;
+      color: #abd6ff;
+      font-weight: bold;
+      background-image: url(@/assets/images/title-bg2.png);
+      background-size: 100% 100%;
+    }
+
+    .device {
+      display: flex;
+      flex-direction: column;
+      padding: 10px 0;
+      height: 248px;
+      .device_box {
+        flex: 1;
+        display: flex;
+        justify-content: space-evenly;
+        align-items: center;
+
+        .box_msg {
+          display: flex;
+          flex-direction: column;
+          justify-content: space-evenly;
+          align-items: center;
+          height: 100%;
+          .msg_num {
+            // margin-bottom: 10px;
+            font-size: 12px;
+            animation: float 3s linear infinite;
+
+            span {
+              font-size: 20px;
+              font-weight: bold;
+            }
+          }
+          .msg_icon {
+            position: relative;
+            width: 58px;
+            height: 36px;
+            background-image: url(@/assets/images/device-icon2.png);
+            background-size: 100% 100%;
+
+            img {
+              position: absolute;
+              top: -46px;
+              left: 6px;
+              width: 44px;
+              height: 81px;
+            }
+          }
+
+          .msg_name {
+            font-size: 14px;
+            font-weight: bold;
+          }
+        }
+
+        .box_content {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          padding: 0 8px;
+          width: 144px;
+          height: 49px;
+          font-size: 12px;
+          background-image: linear-gradient(
+            to right,
+            rgba(7, 27, 51, 0.72),
+            rgba(7, 27, 51, 0)
+          );
+
+          .content_num {
+            color: #2acfff;
+            font-size: 16px;
+            font-weight: bold;
+          }
+        }
+      }
+    }
+
+    .form_water {
+      margin-bottom: 6px;
+      padding-top: 15px;
+      height: 260px;
+      overflow: hidden;
+    }
+
+    .form_ele {
+      padding-top: 15px;
+      height: 260px;
+      overflow: hidden;
+    }
+  }
+}
+
+/*最外层透明*/
+.form ::v-deep(.el-table),
+.form ::v-deep(.el-table__expanded-cell) {
+  background-color: transparent;
+  color: white;
+  border: none;
+}
+/* 表格内背景颜色 */
+.form ::v-deep(.el-table th),
+.form ::v-deep(.el-table tr),
+.form ::v-deep(.el-table td),
+::v-deep(.el-table th.el-table__cell.is-leaf) {
+  background-color: transparent !important;
+  color: white;
+  font-size: 12px;
+  border-color: rgba(255, 255, 255, 0.1);
+  height: 40px;
+}
+
+// 表格底部白线清除
+::v-deep(.el-table__inner-wrapper::before) {
+  height: 0;
+}
+
+// 修改表头背景颜色
+::v-deep(.el-table__header) {
+  background-color: rgba(58, 126, 199, 0.5);
+}
+// 清除表格默认padding
+::v-deep(.el-table .cell) {
+  padding: 0;
+}
+
+// 动画滚动
+::v-deep(.el-scrollbar__view) {
+  animation: run 12s linear infinite;
+}
+</style>

+ 95 - 0
src/components/header.vue

@@ -0,0 +1,95 @@
+<template>
+  <div class="header">
+    <div class="header_box">
+      <div class="box_time">
+        <span class="date">日期:{{ currentDate }}</span>
+        <span>时间:{{ currentTime }}</span>
+      </div>
+      <div class="box_title">万载三中领导驾驶舱</div>
+      <div class="box_weather">
+        <span>天气:晴 4℃~20℃</span>
+        <span class="air">空气质量:优</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, onUnmounted } from "vue";
+import { getCurrentTime } from "@/utils/getTime";
+
+// 当前日期
+const currentDate = ref();
+// 当前时间
+const currentTime = ref();
+// 定时器标识
+const timer = ref();
+
+onMounted(() => {
+  getTime();
+  timer.value = setInterval(() => {
+    getTime();
+  }, 1000);
+});
+
+onUnmounted(() => {
+  clearInterval(timer.value);
+});
+
+// 获取当前日期时间
+const getTime = () => {
+  currentDate.value = getCurrentTime().currentDate;
+  currentTime.value = getCurrentTime().currentTime;
+};
+</script>
+
+<style lang="scss" scoped>
+.header {
+  position: relative;
+  height: 83px;
+  color: #fff;
+  font-family: "庞门正道标题体";
+  background-image: linear-gradient(rgba(1, 15, 15, 1), rgba(11, 32, 38, 0.4));
+
+  .header_box {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin: auto;
+    width: 1870px;
+    height: 83px;
+    font-size: 20px;
+    background-image: url(@/assets/images/header-bg.png);
+    background-size: cover;
+
+    .box_time {
+      margin-top: 17px;
+      width: 460px;
+
+      .date {
+        margin: 0 86px 0 11px;
+      }
+    }
+
+    .box_title {
+      margin-left: -15px;
+      font-size: 52px;
+      background-image: linear-gradient(
+        rgba(255, 255, 255, 1),
+        rgba(0, 128, 163, 1)
+      );
+      background-clip: text;
+      -webkit-background-clip: text;
+      -webkit-text-fill-color: transparent;
+    }
+
+    .box_weather {
+      margin-top: 17px;
+
+      .air {
+        margin: 0 75px 0 85px;
+      }
+    }
+  }
+}
+</style>

File diff suppressed because it is too large
+ 386 - 0
src/components/school/schoolLeft.vue


+ 345 - 0
src/components/school/schoolRight.vue

@@ -0,0 +1,345 @@
+<template>
+  <div class="container">
+    <!-- 标题区域 -->
+    <div class="title">
+      <div class="title_text">校园安全</div>
+      <div class="title_sub">Campus security</div>
+    </div>
+    <!-- 内容区域 -->
+    <div class="content">
+      <div class="sub_title">安全设施监控情况</div>
+      <div class="device">
+        <div class="device_box">
+          <div class="box_img">
+            <div class="box_num" ref="deviceDom">{{ deviceNum }}</div>
+          </div>
+          监控摄像头
+        </div>
+
+        <div class="device_box">
+          <div class="box_img">
+            <div class="box_num" ref="deviceDom2">{{ deviceNum2 }}</div>
+          </div>
+          报警器/监控器
+        </div>
+
+        <div class="device_box">
+          <div class="box_img">
+            <div class="box_num" ref="deviceDom3">{{ deviceNum3 }}</div>
+          </div>
+          消防设施
+        </div>
+      </div>
+      <div class="sub_title">预警推送</div>
+      <div class="form">
+        <el-table :data="tableData">
+          <el-table-column label="序号" align="center" width="40">
+            <template #default="scope">
+              <div class="order">{{ scope.$index + 1 }}</div>
+            </template>
+          </el-table-column>
+
+          <el-table-column
+            prop="name"
+            label="姓名"
+            align="center"
+            width="50"
+            show-overflow-tooltip
+          />
+          <el-table-column prop="time" label="时间" align="center" width="55" />
+          <el-table-column
+            prop="type"
+            label="类别"
+            align="center"
+            width="62"
+            show-overflow-tooltip
+          />
+          <el-table-column
+            prop="section"
+            label="部门"
+            align="center"
+            width="62"
+          />
+          <el-table-column
+            prop="address"
+            label="地点"
+            align="center"
+            width="71"
+            show-overflow-tooltip
+          />
+          <el-table-column label="状态" align="center" width="62">
+            <template #default="{ row }">
+              <div v-if="row.status === 0">未处理</div>
+              <div class="red" v-if="row.status === 1">已推送</div>
+              <div class="green" v-if="row.status === 2">已处理</div>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div class="sub_title">
+        实时监控画面
+        <span class="more" @click="handleCheckMore">查看更多 ></span>
+      </div>
+      <div class="video">
+        <div class="video_box" v-for="(item, index) in 4" :key="index">
+          <img
+            src="https://materials.cdn.bcebos.com/images/107505592/68889f5dbf4bd8d8ef0cd11f98b5adea.jpeg"
+          />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import { useRouter } from "vue-router";
+import { countUpNum } from "@/utils/countUpNum";
+
+const deviceNum = ref(100);
+const deviceNum2 = ref(150);
+const deviceNum3 = ref(10);
+const deviceDom = ref();
+const deviceDom2 = ref();
+const deviceDom3 = ref();
+
+const tableData = [
+  {
+    name: "张三",
+    time: "18:22",
+    type: "打架打架打架",
+    section: "八年级",
+    address: "教学楼教学楼教学楼",
+    status: 0,
+  },
+  {
+    name: "李四",
+    time: "18:22",
+    type: "打架",
+    section: "八年级",
+    address: "教学楼",
+    status: 1,
+  },
+  {
+    name: "王五",
+    time: "18:22",
+    type: "打架",
+    section: "八年级",
+    address: "教学楼",
+    status: 2,
+  },
+  {
+    name: "老刘",
+    time: "18:22",
+    type: "打架打架打架",
+    section: "八年级",
+    address: "教学楼教学楼教学楼",
+    status: 0,
+  },
+  {
+    name: "胜七",
+    time: "18:22",
+    type: "打架",
+    section: "八年级",
+    address: "教学楼",
+    status: 1,
+  },
+  {
+    name: "王八",
+    time: "18:22",
+    type: "打架",
+    section: "八年级",
+    address: "教学楼",
+    status: 2,
+  },
+  {
+    name: "何九",
+    time: "18:22",
+    type: "打架打架打架",
+    section: "八年级",
+    address: "教学楼教学楼教学楼",
+    status: 0,
+  },
+];
+
+const router = useRouter();
+
+onMounted(() => {
+  getCountUpNum();
+});
+
+// 查看更多按钮回调
+const handleCheckMore = () => {
+  router.push("/more");
+};
+
+const getCountUpNum = () => {
+  countUpNum(deviceDom.value, deviceNum.value);
+  countUpNum(deviceDom2.value, deviceNum2.value);
+  countUpNum(deviceDom3.value, deviceNum3.value);
+};
+</script>
+
+<style lang="scss" scoped>
+.container {
+  width: 435px;
+  height: 951px;
+  color: #fff;
+  background-image: url(@/assets/images/box-bg.png);
+
+  .title {
+    display: flex;
+    align-items: center;
+    width: 430px;
+    height: 47px;
+    font-family: "庞门正道标题体";
+    background-image: url(@/assets/images/title-bg.png);
+    background-size: 100% 100%;
+
+    .title_text {
+      margin-left: 38px;
+      font-size: 20px;
+      text-shadow: 0px 0px 9px #158eff;
+    }
+
+    .title_sub {
+      margin-left: 19px;
+      font-size: 12px;
+      color: #215a8e;
+    }
+  }
+
+  .content {
+    padding: 20px 19px 0 14px;
+    height: 904px;
+
+    .sub_title {
+      display: flex;
+      padding-left: 28px;
+      height: 35px;
+      line-height: 18px;
+      color: #abd6ff;
+      font-weight: bold;
+      background-image: url(@/assets/images/title-bg2.png);
+      background-size: 100% 100%;
+
+      .more {
+        margin-left: auto;
+        font-size: 12px;
+        cursor: pointer;
+      }
+    }
+
+    .device {
+      display: flex;
+      justify-content: space-evenly;
+      align-items: center;
+      height: 130px;
+
+      .device_box {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        font-size: 14px;
+
+        .box_img {
+          margin-bottom: 5px;
+          width: 80px;
+          height: 48px;
+          font-size: 19px;
+          font-weight: bold;
+          text-align: center;
+          background-image: url(@/assets/images/device.png);
+          background-size: 100% 100%;
+
+          .box_num {
+            animation: float 3s linear infinite;
+          }
+        }
+      }
+    }
+
+    .form {
+      margin: 26px 0;
+      height: 274px;
+      overflow: hidden;
+
+      .order {
+        margin: auto;
+        width: 24px;
+        height: 16px;
+        line-height: 16px;
+        text-align: center;
+        color: #ffeb3b;
+        background-color: rgba(255, 235, 59, 0.3);
+      }
+
+      .red {
+        color: #ff6b6b;
+      }
+
+      .green {
+        color: #40f7eb;
+      }
+    }
+
+    .video {
+      display: flex;
+      flex-wrap: wrap;
+      justify-content: space-between;
+      align-content: space-evenly;
+      padding-top: 24px;
+      height: 320px;
+
+      .video_box {
+        width: 198px;
+        height: 134px;
+        border-radius: 5px;
+
+        img {
+          width: 100%;
+          border-radius: 5px;
+        }
+      }
+    }
+  }
+}
+
+/*最外层透明*/
+.form ::v-deep(.el-table),
+.form ::v-deep(.el-table__expanded-cell) {
+  background-color: transparent;
+  color: white;
+  border: none;
+}
+/* 表格内背景颜色 */
+.form ::v-deep(.el-table th),
+.form ::v-deep(.el-table tr),
+.form ::v-deep(.el-table td),
+::v-deep(.el-table th.el-table__cell.is-leaf) {
+  background-color: transparent !important;
+  color: white;
+  font-size: 12px;
+  border-color: rgba(255, 255, 255, 0.1);
+  height: 40px;
+}
+
+// 表格底部白线清除
+::v-deep(.el-table__inner-wrapper::before) {
+  height: 0;
+}
+
+// 修改表头背景颜色
+::v-deep(.el-table__header) {
+  background-color: rgba(58, 126, 199, 0.5);
+}
+// 清除表格默认padding
+::v-deep(.el-table .cell) {
+  padding: 0;
+}
+
+// 动画滚动
+::v-deep(.el-scrollbar__view) {
+  animation: run 12s linear infinite;
+}
+</style>

+ 412 - 0
src/components/student/studentCenter.vue

@@ -0,0 +1,412 @@
+<template>
+  <div class="container">
+    <!-- 雷达图区域 -->
+    <div class="top">
+      <div class="top_chart" ref="radarChart"></div>
+
+      <!-- 教师寄语区域 -->
+      <div class="top_msg">
+        <div class="msg_title">教师寄语</div>
+        <div class="msg_detail">
+          老师认为你是一个很需要压力的人,有压力才会有进步,你在校表现良好,也乐于助人,但学习上对自己不够严格,慵慵懒懒的,要进步首先要端正学习态度,多动脑动手只有这样才能获得满意的成绩!
+        </div>
+      </div>
+    </div>
+
+    <!-- 日常轨迹区域 -->
+    <div class="bottom">
+      <div class="bottom_title">
+        <img src="@/assets/images/box-icon.png" />
+        日常轨迹
+      </div>
+
+      <!-- 轨迹线展示区域 -->
+      <div class="bottom_box">
+        <div
+          v-for="(item, index) in 7"
+          :key="index"
+          :class="index % 2 === 0 ? 'bottom_line' : 'bottom_line2'"
+        >
+          <div class="circle">
+            <!-- 详细信息区域 -->
+            <div class="detail">
+              <div class="detail_img">
+                <el-image
+                  style="width: 38px; height: 45px"
+                  src="https://img1.baidu.com/it/u=1398895526,1958525606&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800"
+                  :preview-src-list="[
+                    'https://img1.baidu.com/it/u=1398895526,1958525606&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800',
+                  ]"
+                  fit="cover"
+                />
+              </div>
+              <div>
+                <div>地址:</div>
+                <div
+                  class="detail_address"
+                  title="江西省南昌市经开区黄家湖文化大道"
+                >
+                  江西省南昌市经开区黄家湖文化大道
+                </div>
+              </div>
+            </div>
+
+            <!-- 时间区域 -->
+            <div class="img">
+              <span class="span">
+                <el-icon size="12"><Clock /></el-icon>
+              </span>
+              <span class="text">9.20&nbsp;&nbsp;08:00</span>
+            </div>
+          </div>
+          <span>—</span>
+          <div class="small"></div>
+          <span>—</span>
+          <div class="small"></div>
+          <span>—</span>
+          <div class="small"></div>
+          <span>—</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import * as Echarts from "echarts";
+
+let myRaChart: any;
+
+const radarChart = ref(null);
+
+onMounted(() => {
+  myRaChart = Echarts.init(radarChart.value);
+  initRaChart();
+});
+
+// 初始化雷达图
+const initRaChart = () => {
+  const options = {
+    tooltip: {
+      trigger: "item",
+    },
+    radar: {
+      indicator: [
+        { name: "语文", max: 150 },
+        { name: "数学", max: 150 },
+        { name: "英语", max: 150 },
+        { name: "地理", max: 150 },
+        { name: "政治", max: 150 },
+        { name: "历史", max: 150 },
+        { name: "化学", max: 150 },
+        { name: "物理", max: 150 },
+      ],
+      axisName: {
+        color: "#fff",
+        fontSize: 19.5,
+      },
+      splitArea: {
+        areaStyle: {
+          color: "none",
+        },
+      },
+      axisLine: {
+        lineStyle: {
+          color: "rgba(255, 255, 255, 1)",
+        },
+      },
+      splitLine: {
+        lineStyle: {
+          color: "rgba(255, 255, 255, 1)",
+        },
+      },
+    },
+    series: [
+      {
+        type: "radar",
+        name: "学习成绩",
+        data: [
+          {
+            value: [150, 120, 99, 78, 52, 96, 82, 86],
+            areaStyle: {
+              color: "rgba(0, 209, 200, 0.5)",
+            },
+            symbol: "none",
+          },
+        ],
+      },
+    ],
+    color: "rgba(0, 209, 200, 1)",
+  };
+
+  myRaChart.setOption(options);
+};
+</script>
+
+<style lang="scss" scoped>
+.container {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  width: 800px;
+  height: 834px;
+  color: #fff;
+
+  .top {
+    height: 530px;
+    background-image: url(@/assets/images/box-bg6.png);
+    background-size: 100% 100%;
+
+    .top_chart {
+      height: 407px;
+    }
+
+    .top_msg {
+      margin: auto;
+      padding: 0 18px;
+      width: 442px;
+      height: 123px;
+      background-color: rgba(212, 234, 250, 0.1);
+      border: 1px solid rgba(212, 234, 250, 0.2);
+
+      .msg_title {
+        margin: 10px 0;
+        font-size: 16px;
+        font-weight: bold;
+      }
+
+      .msg_detail {
+        font-size: 12px;
+        line-height: 19px;
+        display: -webkit-box;
+        -webkit-box-orient: vertical;
+        -webkit-line-clamp: 4;
+        overflow: hidden;
+      }
+    }
+  }
+
+  .bottom {
+    padding: 17px 10px 0 22px;
+    height: 272px;
+    background-image: url(@/assets/images/box-bg4.png);
+    background-size: 100% 100%;
+
+    .bottom_title {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      font-weight: bold;
+
+      img {
+        margin-right: 12px;
+        width: 24px;
+        height: 24px;
+      }
+    }
+
+    .bottom_box {
+      display: flex;
+      height: 220px;
+      overflow-x: auto;
+      overflow-y: hidden;
+
+      .bottom_line {
+        display: inline-flex;
+        align-items: center;
+        height: 220px;
+
+        .circle {
+          position: relative;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          padding: 2px;
+          width: 18px;
+          height: 18px;
+          border-radius: 50%;
+          border: 1px solid #70b7fa;
+          background-color: #70b7fa;
+          background-clip: content-box;
+
+          .img {
+            position: absolute;
+            top: 82px;
+            left: 0;
+            display: flex;
+            align-items: center;
+            width: 121px;
+            height: 18px;
+            font-size: 10px;
+            background-image: url(@/assets/images/line-bg.png);
+            background-size: 100% 100%;
+
+            .span {
+              margin-top: 2px;
+              margin-left: 20px;
+              margin-right: 10px;
+              color: #fff;
+            }
+
+            .text {
+              color: #fff;
+            }
+          }
+
+          .detail {
+            position: absolute;
+            top: 22px;
+            left: 8px;
+            display: flex;
+            padding: 6px;
+            width: 111px;
+            height: 57px;
+            color: #fff;
+            font-size: 10px;
+            background-color: rgba(112, 183, 250, 0.4);
+
+            .detail_img {
+              margin-right: 4px;
+              width: 38px;
+              height: 45px;
+            }
+
+            .detail_address {
+              margin-top: 4px;
+              cursor: pointer;
+              display: -webkit-box;
+              -webkit-box-orient: vertical;
+              -webkit-line-clamp: 2;
+              overflow: hidden;
+            }
+          }
+        }
+        .circle::after {
+          content: "";
+          position: relative;
+          top: 37px;
+          left: 0;
+          height: 74px;
+          border-left: 1px solid #70b7fa;
+        }
+
+        span {
+          color: #70b7fa;
+        }
+
+        .small {
+          margin-top: 4px;
+          width: 6px;
+          height: 6px;
+          border-radius: 50%;
+          background-color: #70b7fa;
+        }
+      }
+
+      .bottom_line2 {
+        display: inline-flex;
+        align-items: center;
+        height: 220px;
+
+        .circle {
+          position: relative;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          padding: 2px;
+          width: 18px;
+          height: 18px;
+          border-radius: 50%;
+          border: 1px solid #70b7fa;
+          background-color: #70b7fa;
+          background-clip: content-box;
+
+          .img {
+            position: absolute;
+            top: -82px;
+            left: 0;
+            display: flex;
+            align-items: center;
+            width: 121px;
+            height: 18px;
+            font-size: 10px;
+            background-image: url(@/assets/images/line-bg.png);
+            background-size: 100% 100%;
+
+            .span {
+              margin-top: 2px;
+              margin-left: 20px;
+              margin-right: 10px;
+              color: #fff;
+            }
+
+            .text {
+              color: #fff;
+            }
+          }
+
+          .detail {
+            position: absolute;
+            top: -61px;
+            left: 8px;
+            display: flex;
+            padding: 6px;
+            width: 111px;
+            height: 57px;
+            color: #fff;
+            font-size: 10px;
+            background-color: rgba(112, 183, 250, 0.4);
+
+            .detail_img {
+              margin-right: 4px;
+              width: 38px;
+              height: 45px;
+            }
+
+            .detail_address {
+              margin-top: 4px;
+              cursor: pointer;
+              display: -webkit-box;
+              -webkit-box-orient: vertical;
+              -webkit-line-clamp: 2;
+              overflow: hidden;
+            }
+          }
+        }
+        .circle::after {
+          content: "";
+          position: relative;
+          top: -37px;
+          left: 0;
+          height: 74px;
+          border-left: 1px solid #70b7fa;
+        }
+
+        span {
+          color: #70b7fa;
+        }
+
+        .small {
+          margin-top: 4px;
+          width: 6px;
+          height: 6px;
+          border-radius: 50%;
+          background-color: #70b7fa;
+        }
+      }
+    }
+  }
+}
+
+/*定义滚动条样式(高宽及背景)*/
+::-webkit-scrollbar {
+  height: 10px;
+}
+
+/*定义滑块 样式*/
+::-webkit-scrollbar-thumb {
+  border-radius: 3px;
+  background-color: rgba(2, 27, 41, 0.8);
+}
+</style>

+ 378 - 0
src/components/student/studentLeft.vue

@@ -0,0 +1,378 @@
+<template>
+  <div class="container">
+    <!-- 基本信息区域 -->
+    <div class="top">
+      <div class="top_tilte">
+        <img src="@/assets/images/box-icon.png" />
+        基本信息
+      </div>
+      <div class="top_detail">
+        <div class="detail_photo">
+          <img
+            class="img"
+            src="https://img1.baidu.com/it/u=1398895526,1958525606&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800"
+          />
+          <div class="photo_icon">
+            <el-icon size="20" color="#2493F1"><ArrowDownBold /></el-icon>
+          </div>
+        </div>
+        <div class="detail_msg">
+          <div>姓名:张晓晓</div>
+          <div>性别:男</div>
+          <div>编号:2216161</div>
+          <div>部门:学生</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 考勤情况区域 -->
+    <div class="bottom">
+      <div class="bottom_title">
+        <img src="@/assets/images/box-icon.png" />
+        考勤情况
+      </div>
+
+      <!-- 数据展示区域 -->
+      <div class="bottom_msg">
+        <div class="msg_change">
+          <div
+            class="change_box"
+            :class="currentTimeRang === 0 ? 'active' : ''"
+            @click="changeTimeRang(0)"
+          >
+            本学期
+          </div>
+          <div
+            class="change_box"
+            :class="currentTimeRang === 1 ? 'active' : ''"
+            @click="changeTimeRang(1)"
+          >
+            本周
+          </div>
+          <div
+            class="change_box"
+            :class="currentTimeRang === 2 ? 'active' : ''"
+            @click="changeTimeRang(2)"
+          >
+            本月
+          </div>
+        </div>
+
+        <div class="msg_detail">
+          <div class="detail_box">
+            <div class="box_num" ref="valueDom">{{ value }}</div>
+            <div class="box_type">准时</div>
+          </div>
+          <div class="detail_box">
+            <div class="box_num" ref="valueDom2">{{ value2 }}</div>
+            <div class="box_type">请假</div>
+          </div>
+          <div class="detail_box">
+            <div class="box_num" ref="valueDom3">{{ value3 }}</div>
+            <div class="box_type">迟到</div>
+          </div>
+          <div class="detail_box">
+            <div class="box_num" ref="valueDom4">{{ value4 }}</div>
+            <div class="box_type">超时打卡</div>
+          </div>
+          <div class="detail_box">
+            <div class="box_num" ref="valueDom5">{{ value5 }}</div>
+            <div class="box_type">未打卡</div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 图表展示区域 -->
+      <div class="bottom_chart" ref="pieChart">123</div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import * as Echarts from "echarts";
+import { countUpNum } from "@/utils/countUpNum";
+
+const value = ref(50);
+const value2 = ref(50);
+const value3 = ref(50);
+const value4 = ref(50);
+const value5 = ref(50);
+
+const valueDom = ref();
+const valueDom2 = ref();
+const valueDom3 = ref();
+const valueDom4 = ref();
+const valueDom5 = ref();
+
+let myPieChart: any;
+const pieChart = ref();
+
+// 切换考勤统计时间 0本学期 1本周 2本月
+const currentTimeRang = ref(0);
+
+const chartData = ref([
+  { value: 5, name: "准时" },
+  { value: 6, name: "请假" },
+  { value: 3, name: "迟到" },
+  { value: 2, name: "超时打卡" },
+  { value: 1, name: "未打卡" },
+]);
+
+onMounted(() => {
+  myPieChart = Echarts.init(pieChart.value);
+  initPieChart();
+
+  getCountUpNum();
+});
+
+const initPieChart = () => {
+  const options = {
+    title: {
+      text: "总次数",
+      left: "22%",
+      top: "38%",
+      textStyle: {
+        fontSize: 14,
+        color: "#D5E3EA",
+      },
+      subtext: "200",
+      subtextStyle: {
+        fontSize: 18,
+        color: "#D5E3EA",
+        align: "center",
+        fontWeight: "bold",
+      },
+      itemGap: 15,
+    },
+    tooltip: {
+      trigger: "item",
+    },
+    legend: {
+      orient: "vertical",
+      right: "4%",
+      top: "center",
+      itemWidth: 8,
+      itemHeight: 8,
+      itemGap: 25,
+      textStyle: {
+        padding: 8,
+        color: "#fff",
+        fontSize: 14,
+        rich: {
+          a: {
+            fontSize: 16,
+            fontWeight: "bold",
+            align: "right",
+            padding: [0, -100, 0, 0],
+          },
+        },
+      },
+      formatter: (params: any) => {
+        const valueObj: any = chartData.value.find(
+          (item) => item.name === params
+        );
+        return params + "{a|" + valueObj.value + "}";
+      },
+    },
+    color: ["#50A4E1", "#FF85BE", "#FFC77D", "#93DE62", "#4AE8E8"],
+    series: [
+      {
+        name: "考勤情况",
+        type: "pie",
+        center: ["28%", "50%"],
+        radius: ["55%", "75%"],
+        avoidLabelOverlap: false,
+        label: {
+          show: false,
+        },
+        itemStyle: {
+          borderColor: "rgba(2, 27, 41, 0.1)",
+          borderWidth: 5,
+        },
+        data: chartData.value,
+      },
+    ],
+  };
+
+  myPieChart.setOption(options);
+};
+// 考勤统计切换时间范围按钮回调
+const changeTimeRang = (value: number) => {
+  if (currentTimeRang.value !== value) {
+    if (value === 0) {
+      currentTimeRang.value = 0;
+    } else if (value === 1) {
+      currentTimeRang.value = 1;
+    } else if (value === 2) {
+      currentTimeRang.value = 2;
+    }
+  }
+};
+
+// 让数字跳动
+const getCountUpNum = () => {
+  countUpNum(valueDom.value, value.value);
+  countUpNum(valueDom2.value, value2.value);
+  countUpNum(valueDom3.value, value3.value);
+  countUpNum(valueDom4.value, value4.value);
+  countUpNum(valueDom5.value, value5.value);
+};
+</script>
+
+<style lang="scss" scoped>
+.container {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  width: 500px;
+  height: 834px;
+  color: #fff;
+
+  .top {
+    padding: 17px 0 0 22px;
+    height: 272px;
+    background-image: url(@/assets/images/box-bg2.png);
+    background-size: 100% 100%;
+
+    .top_tilte {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      font-weight: bold;
+
+      img {
+        margin-right: 12px;
+        width: 24px;
+        height: 24px;
+      }
+    }
+
+    .top_detail {
+      margin-top: 40px;
+      display: flex;
+      align-items: center;
+
+      .detail_photo {
+        position: relative;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        width: 122px;
+        height: 122px;
+        border-radius: 50%;
+        background-color: #2493f1;
+
+        img {
+          width: 112px;
+          height: 112px;
+          border-radius: 50%;
+          object-fit: cover;
+        }
+
+        .photo_icon {
+          position: absolute;
+          top: 45px;
+          right: -14px;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 34px;
+          height: 34px;
+          border-radius: 50%;
+          background-color: #fff;
+        }
+      }
+
+      .detail_msg {
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+        margin-left: 63px;
+        height: 122px;
+        font-size: 14px;
+      }
+    }
+  }
+
+  .bottom {
+    padding: 17px 54px 0 22px;
+    height: 553px;
+    background-image: url(@/assets/images/box-bg3.png);
+    background-size: 100% 100%;
+
+    .bottom_title {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      font-weight: bold;
+
+      img {
+        margin-right: 12px;
+        width: 24px;
+        height: 24px;
+      }
+    }
+
+    .bottom_msg {
+      height: 262px;
+      overflow: hidden;
+
+      .msg_change {
+        display: flex;
+        justify-content: space-between;
+        margin: 20px 0 33px 220px;
+        padding: 2px;
+        width: 200px;
+        height: 32px;
+        font-size: 14px;
+        border-radius: 2px;
+        border: 1px solid #9c9c9c;
+
+        .change_box {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 61px;
+          border-radius: 2px;
+          background-color: rgba(114, 151, 179, 0.3);
+          cursor: pointer;
+        }
+
+        .active {
+          border: 1px solid #70b7fa;
+          background-color: rgba(114, 151, 179, 1);
+        }
+      }
+
+      .msg_detail {
+        display: flex;
+        flex-wrap: wrap;
+        justify-content: space-between;
+
+        .detail_box {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          width: 25%;
+          height: 80px;
+
+          .box_num {
+            font-size: 28px;
+            font-weight: bold;
+          }
+
+          .box_type {
+            margin-top: 4px;
+            font-size: 14px;
+          }
+        }
+      }
+    }
+
+    .bottom_chart {
+      height: 248px;
+    }
+  }
+}
+</style>

+ 288 - 0
src/components/student/studentRight.vue

@@ -0,0 +1,288 @@
+<template>
+  <div class="container">
+    <!-- 成绩统计区域 -->
+    <div class="container_box">
+      <div class="box_title">
+        <img src="@/assets/images/box-icon.png" />
+        成绩统计
+      </div>
+
+      <!-- 筛选框区域 -->
+      <div class="box_select">
+        <span>考试名称</span>
+        <el-select
+          v-model="value"
+          placeholder="请选择学期"
+          style="width: 206px"
+        >
+          <el-option
+            v-for="item in optionsTime"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </div>
+
+      <!-- 图表展示区域 -->
+      <div class="box_chart" ref="barChart"></div>
+    </div>
+
+    <!-- 考评记录区域 -->
+    <div class="container_box">
+      <div class="box_title">
+        <img src="@/assets/images/box-icon.png" />
+        考评记录
+      </div>
+
+      <div class="box_form form">
+        <el-table :data="tableData">
+          <el-table-column
+            prop="time"
+            label="考评时间"
+            align="center"
+            width="150"
+          />
+          <el-table-column prop="leave" label="考评等级" align="center" />
+        </el-table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import * as Echarts from "echarts";
+
+let myBarChart: any;
+const barChart = ref();
+
+const value = ref("");
+
+const optionsTime = [
+  {
+    value: "0",
+    label: "2022-2023",
+  },
+  {
+    value: "1",
+    label: "2021-2022",
+  },
+  {
+    value: "2",
+    label: "2020-2021",
+  },
+];
+
+const tableData = [
+  {
+    time: "2020-2021秋季",
+    leave: "A",
+  },
+  {
+    time: "2020-2021秋季",
+    leave: "A",
+  },
+  {
+    time: "2020-2021秋季",
+    leave: "A",
+  },
+  {
+    time: "2020-2021秋季",
+    leave: "A",
+  },
+  {
+    time: "2020-2021秋季",
+    leave: "A",
+  },
+  {
+    time: "2020-2021秋季",
+    leave: "A",
+  },
+  {
+    time: "2020-2021秋季",
+    leave: "A",
+  },
+  {
+    time: "2020-2021秋季",
+    leave: "A",
+  },
+];
+
+onMounted(() => {
+  myBarChart = Echarts.init(barChart.value);
+  initBarChart();
+});
+
+const initBarChart = () => {
+  const options = {
+    tooltip: {
+      trigger: "axis",
+      axisPointer: {
+        type: "shadow",
+      },
+    },
+    grid: {
+      top: "13%",
+      left: "8%",
+      right: "4%",
+      bottom: "18%",
+    },
+    xAxis: {
+      type: "category",
+      data: ["语文", "历史", "数学", "英语", "物理", "化学", "地理", "生物"],
+      axisTick: {
+        show: false,
+      },
+      axisLabel: {
+        margin: 12,
+        interval: 0,
+        fontSize: 14,
+        color: "#fff",
+      },
+    },
+    yAxis: {
+      name: "分数",
+      nameTextStyle: {
+        fontSize: 14,
+        color: "#fff",
+      },
+      type: "value",
+      axisLabel: {
+        color: "#fff",
+      },
+      splitLine: {
+        show: true,
+        lineStyle: {
+          color: "rgba(108, 128, 151, 0.5)",
+        },
+      },
+    },
+    series: [
+      {
+        data: [80, 45, 99, 78, 52, 96, 82, 86],
+        type: "bar",
+        barWidth: "60%",
+        label: {
+          show: true,
+          position: "top",
+          distance: 5,
+          color: "#14B6F3",
+          fontSize: 14,
+        },
+        itemStyle: {
+          color: new Echarts.graphic.LinearGradient(0, 1, 0, 0, [
+            { offset: 0, color: "#0336FF" },
+            { offset: 1, color: "#01B4FF" },
+          ]),
+        },
+      },
+    ],
+  };
+
+  myBarChart.setOption(options);
+};
+</script>
+
+<style lang="scss" scoped>
+.container {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  width: 500px;
+  height: 834px;
+  color: #fff;
+
+  .container_box {
+    padding: 17px 0 0 22px;
+    width: 500px;
+    height: 412px;
+    background-image: url(@/assets/images/box-bg5.png);
+    background-size: 100% 100%;
+
+    .box_title {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      font-weight: bold;
+
+      img {
+        margin-right: 12px;
+        width: 24px;
+        height: 24px;
+      }
+    }
+
+    .box_select {
+      display: flex;
+      align-items: center;
+      margin: 31px 0 31px 170px;
+      font-size: 14px;
+
+      span {
+        margin-right: 13px;
+      }
+    }
+
+    .box_chart {
+      height: 270px;
+    }
+
+    .box_form {
+      padding-right: 44px;
+      margin-top: 48px;
+      height: 287px;
+      overflow: hidden;
+    }
+  }
+}
+
+// 修改select背景颜色
+::v-deep(.el-input__wrapper) {
+  background: transparent;
+  background-color: rgba(48, 75, 95, 0.5);
+}
+
+// 修改select筛选框文字颜色
+::v-deep(.el-input__inner) {
+  color: #fff;
+}
+
+/*最外层透明*/
+.form ::v-deep(.el-table),
+.form ::v-deep(.el-table__expanded-cell) {
+  background-color: transparent;
+  color: white;
+  border: none;
+}
+/* 表格内背景颜色 */
+.form ::v-deep(.el-table th),
+.form ::v-deep(.el-table tr),
+.form ::v-deep(.el-table td),
+::v-deep(.el-table th.el-table__cell.is-leaf) {
+  background-color: transparent !important;
+  color: white;
+  font-size: 12px;
+  border-color: rgba(255, 255, 255, 0.1);
+  height: 40px;
+}
+
+// 表格底部白线清除
+::v-deep(.el-table__inner-wrapper::before) {
+  height: 0;
+}
+
+// 修改表头背景颜色
+::v-deep(.el-table__header) {
+  background-color: rgba(58, 126, 199, 0.5);
+}
+// 清除表格默认padding
+::v-deep(.el-table .cell) {
+  padding: 0;
+}
+
+// 动画滚动
+// ::v-deep(.el-scrollbar__view) {
+//   animation: run 12s linear infinite;
+// }
+</style>

+ 377 - 0
src/components/teacher/teacherCenter.vue

@@ -0,0 +1,377 @@
+<template>
+  <div class="container">
+    <!-- 雷达图区域 -->
+    <div class="top">
+      <div class="top_chart" ref="radarChart"></div>
+    </div>
+
+    <!-- 日常轨迹区域 -->
+    <div class="bottom">
+      <div class="bottom_title">
+        <img src="@/assets/images/box-icon.png" />
+        日常轨迹
+      </div>
+
+      <!-- 轨迹线展示区域 -->
+      <div class="bottom_box">
+        <div
+          v-for="(item, index) in 7"
+          :key="index"
+          :class="index % 2 === 0 ? 'bottom_line' : 'bottom_line2'"
+        >
+          <div class="circle">
+            <!-- 详细信息区域 -->
+            <div class="detail">
+              <div class="detail_img">
+                <el-image
+                  style="width: 38px; height: 45px"
+                  src="https://img1.baidu.com/it/u=1398895526,1958525606&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800"
+                  :preview-src-list="[
+                    'https://img1.baidu.com/it/u=1398895526,1958525606&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800',
+                  ]"
+                  fit="cover"
+                />
+              </div>
+              <div>
+                <div>地址:</div>
+                <div
+                  class="detail_address"
+                  title="江西省南昌市经开区黄家湖文化大道"
+                >
+                  江西省南昌市经开区黄家湖文化大道
+                </div>
+              </div>
+            </div>
+
+            <!-- 时间区域 -->
+            <div class="img">
+              <span class="span">
+                <el-icon size="12"><Clock /></el-icon>
+              </span>
+              <span class="text">9.20&nbsp;&nbsp;08:00</span>
+            </div>
+          </div>
+          <span>—</span>
+          <div class="small"></div>
+          <span>—</span>
+          <div class="small"></div>
+          <span>—</span>
+          <div class="small"></div>
+          <span>—</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import * as Echarts from "echarts";
+
+let myRaChart: any;
+
+const radarChart = ref(null);
+
+onMounted(() => {
+  myRaChart = Echarts.init(radarChart.value);
+  initRaChart();
+});
+
+// 初始化雷达图
+const initRaChart = () => {
+  const options = {
+    tooltip: {
+      trigger: "item",
+    },
+    radar: {
+      indicator: [
+        { name: "勤", max: 100 },
+        { name: "能", max: 100 },
+        { name: "德", max: 100 },
+        { name: "扣", max: 100 },
+        { name: "绩", max: 100 },
+      ],
+      axisName: {
+        color: "#fff",
+        fontSize: 19.5,
+      },
+      splitArea: {
+        areaStyle: {
+          color: "none",
+        },
+      },
+      axisLine: {
+        lineStyle: {
+          color: "rgba(255, 255, 255, 1)",
+        },
+      },
+      splitLine: {
+        lineStyle: {
+          color: "rgba(255, 255, 255, 1)",
+        },
+      },
+    },
+    series: [
+      {
+        type: "radar",
+        name: "综合评价",
+        data: [
+          {
+            value: [100, 92, 99, 78, 52],
+            areaStyle: {
+              color: "rgba(0, 209, 200, 0.5)",
+            },
+            symbol: "none",
+          },
+        ],
+      },
+    ],
+    color: "rgba(0, 209, 200, 1)",
+  };
+
+  myRaChart.setOption(options);
+};
+</script>
+
+<style lang="scss" scoped>
+.container {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  width: 800px;
+  height: 834px;
+  color: #fff;
+
+  .top {
+    height: 530px;
+    background-image: url(@/assets/images/box-bg6.png);
+    background-size: 100% 100%;
+
+    .top_chart {
+      height: 530px;
+    }
+  }
+
+  .bottom {
+    padding: 17px 10px 0 22px;
+    height: 272px;
+    background-image: url(@/assets/images/box-bg4.png);
+    background-size: 100% 100%;
+
+    .bottom_title {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      font-weight: bold;
+
+      img {
+        margin-right: 12px;
+        width: 24px;
+        height: 24px;
+      }
+    }
+
+    .bottom_box {
+      display: flex;
+      height: 220px;
+      overflow-x: auto;
+      overflow-y: hidden;
+
+      .bottom_line {
+        display: inline-flex;
+        align-items: center;
+        height: 220px;
+
+        .circle {
+          position: relative;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          padding: 2px;
+          width: 18px;
+          height: 18px;
+          border-radius: 50%;
+          border: 1px solid #70b7fa;
+          background-color: #70b7fa;
+          background-clip: content-box;
+
+          .img {
+            position: absolute;
+            top: 82px;
+            left: 0;
+            display: flex;
+            align-items: center;
+            width: 121px;
+            height: 18px;
+            font-size: 10px;
+            background-image: url(@/assets/images/line-bg.png);
+            background-size: 100% 100%;
+
+            .span {
+              margin-top: 2px;
+              margin-left: 20px;
+              margin-right: 10px;
+              color: #fff;
+            }
+
+            .text {
+              color: #fff;
+            }
+          }
+
+          .detail {
+            position: absolute;
+            top: 22px;
+            left: 8px;
+            display: flex;
+            padding: 6px;
+            width: 111px;
+            height: 57px;
+            color: #fff;
+            font-size: 10px;
+            background-color: rgba(112, 183, 250, 0.4);
+
+            .detail_img {
+              margin-right: 4px;
+              width: 38px;
+              height: 45px;
+            }
+
+            .detail_address {
+              margin-top: 4px;
+              cursor: pointer;
+              display: -webkit-box;
+              -webkit-box-orient: vertical;
+              -webkit-line-clamp: 2;
+              overflow: hidden;
+            }
+          }
+        }
+        .circle::after {
+          content: "";
+          position: relative;
+          top: 37px;
+          left: 0;
+          height: 74px;
+          border-left: 1px solid #70b7fa;
+        }
+
+        span {
+          color: #70b7fa;
+        }
+
+        .small {
+          margin-top: 4px;
+          width: 6px;
+          height: 6px;
+          border-radius: 50%;
+          background-color: #70b7fa;
+        }
+      }
+
+      .bottom_line2 {
+        display: inline-flex;
+        align-items: center;
+        height: 220px;
+
+        .circle {
+          position: relative;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          padding: 2px;
+          width: 18px;
+          height: 18px;
+          border-radius: 50%;
+          border: 1px solid #70b7fa;
+          background-color: #70b7fa;
+          background-clip: content-box;
+
+          .img {
+            position: absolute;
+            top: -82px;
+            left: 0;
+            display: flex;
+            align-items: center;
+            width: 121px;
+            height: 18px;
+            font-size: 10px;
+            background-image: url(@/assets/images/line-bg.png);
+            background-size: 100% 100%;
+
+            .span {
+              margin-top: 2px;
+              margin-left: 20px;
+              margin-right: 10px;
+              color: #fff;
+            }
+
+            .text {
+              color: #fff;
+            }
+          }
+
+          .detail {
+            position: absolute;
+            top: -61px;
+            left: 8px;
+            display: flex;
+            padding: 6px;
+            width: 111px;
+            height: 57px;
+            color: #fff;
+            font-size: 10px;
+            background-color: rgba(112, 183, 250, 0.4);
+
+            .detail_img {
+              margin-right: 4px;
+              width: 38px;
+              height: 45px;
+            }
+
+            .detail_address {
+              margin-top: 4px;
+              cursor: pointer;
+              display: -webkit-box;
+              -webkit-box-orient: vertical;
+              -webkit-line-clamp: 2;
+              overflow: hidden;
+            }
+          }
+        }
+        .circle::after {
+          content: "";
+          position: relative;
+          top: -37px;
+          left: 0;
+          height: 74px;
+          border-left: 1px solid #70b7fa;
+        }
+
+        span {
+          color: #70b7fa;
+        }
+
+        .small {
+          margin-top: 4px;
+          width: 6px;
+          height: 6px;
+          border-radius: 50%;
+          background-color: #70b7fa;
+        }
+      }
+    }
+  }
+}
+
+/*定义滚动条样式(高宽及背景)*/
+::-webkit-scrollbar {
+  height: 10px;
+}
+
+/*定义滑块 样式*/
+::-webkit-scrollbar-thumb {
+  border-radius: 3px;
+  background-color: rgba(2, 27, 41, 0.8);
+}
+</style>

+ 412 - 0
src/components/teacher/teacherLeft.vue

@@ -0,0 +1,412 @@
+<template>
+  <div class="container">
+    <!-- 基本信息区域 -->
+    <div class="top">
+      <div class="top_tilte">
+        <img src="@/assets/images/box-icon.png" />
+        基本信息
+      </div>
+      <div class="top_detail">
+        <div class="detail_photo">
+          <img
+            class="img"
+            src="https://img1.baidu.com/it/u=1398895526,1958525606&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800"
+          />
+          <div class="photo_icon">
+            <el-icon size="20" color="#2493F1"><ArrowDownBold /></el-icon>
+          </div>
+        </div>
+        <div class="detail_msg">
+          <div>姓名:张晓晓</div>
+          <div>性别:男</div>
+          <div>编号:2216161</div>
+          <div>部门:班主任</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 考勤情况区域 -->
+    <div class="container_box">
+      <div class="box_title">
+        <img src="@/assets/images/box-icon.png" />
+        考勤情况
+      </div>
+
+      <!-- 数据展示区域 -->
+      <div class="box_msg">
+        <div class="msg_change">
+          <div
+            class="change_box"
+            :class="currentTimeRang === 0 ? 'active' : ''"
+            @click="changeTimeRang(0)"
+          >
+            本学期
+          </div>
+          <div
+            class="change_box"
+            :class="currentTimeRang === 1 ? 'active' : ''"
+            @click="changeTimeRang(1)"
+          >
+            本周
+          </div>
+          <div
+            class="change_box"
+            :class="currentTimeRang === 2 ? 'active' : ''"
+            @click="changeTimeRang(2)"
+          >
+            本月
+          </div>
+        </div>
+
+        <div class="msg_detail">
+          <div class="detail_box">
+            <div class="box_num" ref="valueDom">{{ value }}</div>
+            <div class="box_type">正常</div>
+          </div>
+          <div class="detail_box">
+            <div class="box_num" ref="valueDom2">{{ value2 }}</div>
+            <div class="box_type">早退</div>
+          </div>
+          <div class="detail_box">
+            <div class="box_num" ref="valueDom3">{{ value3 }}</div>
+            <div class="box_type">迟到</div>
+          </div>
+          <div class="detail_box">
+            <div class="box_num" ref="valueDom4">{{ value4 }}</div>
+            <div class="box_type">严重迟到</div>
+          </div>
+          <div class="detail_box">
+            <div class="box_num" ref="valueDom5">{{ value5 }}</div>
+            <div class="box_type">旷工迟到</div>
+          </div>
+          <div class="detail_box">
+            <div class="box_num" ref="valueDom6">{{ value6 }}</div>
+            <div class="box_type">未打卡</div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 个人资源上传情况区域 -->
+    <div class="container_box">
+      <div class="box_title">
+        <img src="@/assets/images/box-icon.png" />
+        个人资源上传情况
+      </div>
+
+      <!-- 数据展示区域 -->
+      <div class="box_msg">
+        <div class="msg_change">
+          <div
+            class="change_box"
+            :class="currentTimeRang2 === 0 ? 'active' : ''"
+            @click="changeTimeRang2(0)"
+          >
+            本学期
+          </div>
+          <div
+            class="change_box"
+            :class="currentTimeRang2 === 1 ? 'active' : ''"
+            @click="changeTimeRang2(1)"
+          >
+            本周
+          </div>
+          <div
+            class="change_box"
+            :class="currentTimeRang2 === 2 ? 'active' : ''"
+            @click="changeTimeRang2(2)"
+          >
+            本月
+          </div>
+        </div>
+
+        <div class="msg_detail2">
+          <div class="detail2_box">
+            <img src="@/assets/images/teacher_icon2.png" />
+            <div class="box_content">
+              <div>集体备课资源</div>
+              <div class="text_color" ref="teamValueDom">{{ teamValue }}</div>
+            </div>
+          </div>
+
+          <div class="detail2_line"></div>
+          <div class="detail2_box">
+            <img src="@/assets/images/teacher_icon.png" />
+            <div class="box_content">
+              <div>个人备课资源</div>
+              <div class="text_color2" ref="personValueDom">
+                {{ personValue }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import { countUpNum } from "@/utils/countUpNum";
+
+const value = ref(50);
+const value2 = ref(50);
+const value3 = ref(50);
+const value4 = ref(50);
+const value5 = ref(50);
+const value6 = ref(50);
+
+const valueDom = ref();
+const valueDom2 = ref();
+const valueDom3 = ref();
+const valueDom4 = ref();
+const valueDom5 = ref();
+const valueDom6 = ref();
+
+const teamValue = ref(273);
+const personValue = ref(273);
+
+const teamValueDom = ref(273);
+const personValueDom = ref(273);
+
+// 切换考勤统计时间 0本学期 1本周 2本月
+const currentTimeRang = ref(0);
+
+// 切换个人资源上传情况时间 0本学期 1本周 2本月
+const currentTimeRang2 = ref(0);
+
+onMounted(() => {
+  getCountUpNum();
+});
+
+// 考勤统计切换时间范围按钮回调
+const changeTimeRang = (value: number) => {
+  if (currentTimeRang.value !== value) {
+    if (value === 0) {
+      currentTimeRang.value = 0;
+    } else if (value === 1) {
+      currentTimeRang.value = 1;
+    } else if (value === 2) {
+      currentTimeRang.value = 2;
+    }
+  }
+};
+
+// 个人资源上传情况切换时间范围按钮回调
+const changeTimeRang2 = (value: number) => {
+  if (currentTimeRang2.value !== value) {
+    if (value === 0) {
+      currentTimeRang2.value = 0;
+    } else if (value === 1) {
+      currentTimeRang2.value = 1;
+    } else if (value === 2) {
+      currentTimeRang2.value = 2;
+    }
+  }
+};
+
+// 让数字跳动
+const getCountUpNum = () => {
+  countUpNum(valueDom.value, value.value);
+  countUpNum(valueDom2.value, value2.value);
+  countUpNum(valueDom3.value, value3.value);
+  countUpNum(valueDom4.value, value4.value);
+  countUpNum(valueDom5.value, value5.value);
+  countUpNum(valueDom6.value, value6.value);
+  countUpNum(teamValueDom.value, teamValue.value);
+  countUpNum(personValueDom.value, personValue.value);
+};
+</script>
+
+<style lang="scss" scoped>
+.container {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  width: 500px;
+  height: 834px;
+  color: #fff;
+
+  .top {
+    padding: 17px 0 0 22px;
+    height: 272px;
+    background-image: url(@/assets/images/box-bg2.png);
+    background-size: 100% 100%;
+
+    .top_tilte {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      font-weight: bold;
+
+      img {
+        margin-right: 12px;
+        width: 24px;
+        height: 24px;
+      }
+    }
+
+    .top_detail {
+      margin-top: 40px;
+      display: flex;
+      align-items: center;
+
+      .detail_photo {
+        position: relative;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        width: 122px;
+        height: 122px;
+        border-radius: 50%;
+        background-color: #2493f1;
+
+        img {
+          width: 112px;
+          height: 112px;
+          border-radius: 50%;
+          object-fit: cover;
+        }
+
+        .photo_icon {
+          position: absolute;
+          top: 45px;
+          right: -14px;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 34px;
+          height: 34px;
+          border-radius: 50%;
+          background-color: #fff;
+        }
+      }
+
+      .detail_msg {
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+        margin-left: 63px;
+        height: 122px;
+        font-size: 14px;
+      }
+    }
+  }
+
+  .container_box {
+    padding: 17px 0 0 22px;
+    height: 272px;
+    background-image: url(@/assets/images/box-bg2.png);
+    background-size: 100% 100%;
+
+    .box_title {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      font-weight: bold;
+
+      img {
+        margin-right: 12px;
+        width: 24px;
+        height: 24px;
+      }
+    }
+
+    .box_msg {
+      height: 262px;
+      overflow: hidden;
+
+      .msg_change {
+        display: flex;
+        justify-content: space-between;
+        margin: 16px 0 20px 220px;
+        padding: 2px;
+        width: 200px;
+        height: 32px;
+        font-size: 14px;
+        border-radius: 2px;
+        border: 1px solid #9c9c9c;
+
+        .change_box {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 61px;
+          border-radius: 2px;
+          background-color: rgba(114, 151, 179, 0.3);
+          cursor: pointer;
+        }
+
+        .active {
+          border: 1px solid #70b7fa;
+          background-color: rgba(114, 151, 179, 1);
+        }
+      }
+
+      .msg_detail {
+        display: flex;
+        flex-wrap: wrap;
+
+        .detail_box {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          width: 25%;
+          height: 80px;
+
+          .box_num {
+            font-size: 28px;
+            font-weight: bold;
+          }
+
+          .box_type {
+            margin-top: 4px;
+            font-size: 14px;
+          }
+        }
+      }
+
+      .msg_detail2 {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding-left: 22px;
+        padding-right: 58px;
+        height: 160px;
+
+        .detail2_box {
+          display: flex;
+          font-size: 16px;
+          font-weight: bold;
+
+          img {
+            margin-right: 14px;
+            width: 48px;
+            height: 48px;
+            object-fit: cover;
+          }
+
+          .box_content {
+            display: flex;
+            flex-direction: column;
+            justify-content: space-between;
+            .text_color {
+              color: #ffbf00;
+            }
+
+            .text_color2 {
+              color: #2bff80;
+            }
+          }
+        }
+
+        .detail2_line {
+          width: 1px;
+          height: 40px;
+          border-left: 1px solid rgba(230, 239, 242, 0.2);
+        }
+      }
+    }
+  }
+}
+</style>

+ 445 - 0
src/components/teacher/teacherRight.vue

@@ -0,0 +1,445 @@
+<template>
+  <div class="container">
+    <!-- 教学成果区域 -->
+    <div class="container_box">
+      <div class="box_title">
+        <img src="@/assets/images/box-icon.png" />
+        教学成果
+      </div>
+
+      <!-- 筛选框区域 -->
+      <div class="box_select">
+        <span>部门</span>
+        <el-select
+          v-model="valueClass"
+          placeholder="请选择部门"
+          style="width: 206px"
+        >
+          <el-option
+            v-for="item in optionsClass"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </div>
+
+      <div class="box_select">
+        <span>考试名称</span>
+        <el-select
+          v-model="valueTime"
+          placeholder="请选择学期"
+          style="width: 206px"
+        >
+          <el-option
+            v-for="item in optionsTime"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </div>
+
+      <!-- 图表展示区域 -->
+      <div class="box_chart" ref="pieChart"></div>
+    </div>
+
+    <!-- 排行榜区域 -->
+    <div class="container_box">
+      <div class="box_title">
+        <img src="@/assets/images/box-icon.png" />
+        排行榜
+      </div>
+
+      <div class="box_change">
+        <div
+          class="change_box"
+          :class="currentTimeRang === 0 ? 'active' : ''"
+          @click="changeTimeRang(0)"
+        >
+          本学期
+        </div>
+        <div
+          class="change_box"
+          :class="currentTimeRang === 1 ? 'active' : ''"
+          @click="changeTimeRang(1)"
+        >
+          本周
+        </div>
+        <div
+          class="change_box"
+          :class="currentTimeRang === 2 ? 'active' : ''"
+          @click="changeTimeRang(2)"
+        >
+          本月
+        </div>
+      </div>
+
+      <div class="box_form form">
+        <el-table :data="tableData">
+          <el-table-column label="排名" align="center">
+            <template #default="{ $index }">
+              <div class="form_index color" v-if="$index < 3">
+                NO.{{ $index + 1 }}
+              </div>
+              <div class="form_index color2" v-else>NO.{{ $index + 1 }}</div>
+              <div class="form_icon"></div>
+            </template>
+          </el-table-column>
+          <el-table-column label="姓名" align="center">
+            <template #default="{ row }">
+              <div class="form_name">
+                <img
+                  src="https://img1.baidu.com/it/u=1398895526,1958525606&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800"
+                />
+                {{ row.name }}
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column prop="value" label="战力值" align="center">
+            <template #default="{ row }">
+              <div class="form_value">{{ row.value }}</div>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import * as Echarts from "echarts";
+
+let myPieChart: any;
+const pieChart = ref();
+
+const valueTime = ref("");
+const valueClass = ref("");
+
+// 切换排行榜时间 0本学期 1本周 2本月
+const currentTimeRang = ref(0);
+
+const optionsTime = [
+  {
+    value: "0",
+    label: "2022-2023",
+  },
+  {
+    value: "1",
+    label: "2021-2022",
+  },
+  {
+    value: "2",
+    label: "2020-2021",
+  },
+];
+
+const optionsClass = [
+  {
+    value: "0",
+    label: "八年级一班",
+  },
+  {
+    value: "1",
+    label: "八年级二班",
+  },
+  {
+    value: "2",
+    label: "八年级三班",
+  },
+];
+
+const chartData = ref([
+  { value: 30, name: "60分以下" },
+  { value: 20, name: "60-70 分" },
+  { value: 13, name: "71-80 分" },
+  { value: 12, name: "81-90 分" },
+  { value: 12, name: "91-100分" },
+]);
+
+const tableData = [
+  {
+    name: "张三",
+    value: 35462,
+  },
+  {
+    name: "李四",
+    value: 35462,
+  },
+  {
+    name: "王五",
+    value: 35462,
+  },
+  {
+    name: "老刘",
+    value: 35462,
+  },
+];
+
+onMounted(() => {
+  myPieChart = Echarts.init(pieChart.value);
+  initBarChart();
+});
+
+const initBarChart = () => {
+  const options = {
+    title: {
+      text: "总次数",
+      left: "22%",
+      top: "38%",
+      textStyle: {
+        fontSize: 14,
+        color: "#D5E3EA",
+      },
+      subtext: "{a|200}",
+      subtextStyle: {
+        rich: {
+          a: {
+            padding: 2,
+            fontSize: 18,
+            color: "#D5E3EA",
+            fontWeight: "bold",
+          },
+        },
+      },
+      itemGap: 15,
+    },
+    tooltip: {
+      trigger: "item",
+    },
+    legend: {
+      orient: "vertical",
+      right: "4%",
+      top: "center",
+      itemWidth: 8,
+      itemHeight: 8,
+      itemGap: 25,
+      textStyle: {
+        padding: 8,
+        color: "#fff",
+        fontSize: 14,
+        rich: {
+          a: {
+            fontSize: 16,
+            fontWeight: "bold",
+            align: "right",
+            padding: [0, -150, 0, 0],
+          },
+        },
+      },
+      formatter: (params: any) => {
+        const valueObj: any = chartData.value.find(
+          (item) => item.name === params
+        );
+        return params + "{a|" + valueObj.value + "}";
+      },
+    },
+    color: ["#50A4E1", "#FF85BE", "#FFC77D", "#93DE62", "#4AE8E8"],
+    series: [
+      {
+        name: "教学成绩",
+        type: "pie",
+        center: ["28%", "50%"],
+        radius: ["55%", "75%"],
+        avoidLabelOverlap: false,
+        label: {
+          show: false,
+        },
+        itemStyle: {
+          borderColor: "rgba(2, 27, 41, 0.1)",
+          borderWidth: 5,
+        },
+        data: chartData.value,
+      },
+    ],
+  };
+
+  myPieChart.setOption(options);
+};
+
+// 排行榜切换时间范围按钮回调
+const changeTimeRang = (value: number) => {
+  if (currentTimeRang.value !== value) {
+    if (value === 0) {
+      currentTimeRang.value = 0;
+    } else if (value === 1) {
+      currentTimeRang.value = 1;
+    } else if (value === 2) {
+      currentTimeRang.value = 2;
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.container {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  width: 500px;
+  height: 834px;
+  color: #fff;
+
+  .container_box {
+    padding: 17px 0 0 22px;
+    width: 500px;
+    height: 412px;
+    background-image: url(@/assets/images/box-bg5.png);
+    background-size: 100% 100%;
+
+    .box_title {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      font-weight: bold;
+
+      img {
+        margin-right: 12px;
+        width: 24px;
+        height: 24px;
+      }
+    }
+
+    .box_select {
+      display: flex;
+      justify-content: flex-end;
+      align-items: center;
+      margin-top: 15px;
+      margin-right: 32px;
+      font-size: 14px;
+
+      span {
+        margin-right: 13px;
+      }
+    }
+
+    .box_chart {
+      height: 270px;
+    }
+
+    .box_change {
+      display: flex;
+      justify-content: space-between;
+      margin: 20px 0 33px 220px;
+      padding: 2px;
+      width: 200px;
+      height: 32px;
+      font-size: 14px;
+      border-radius: 2px;
+      border: 1px solid #9c9c9c;
+
+      .change_box {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        width: 61px;
+        border-radius: 2px;
+        background-color: rgba(114, 151, 179, 0.3);
+        cursor: pointer;
+      }
+
+      .active {
+        border: 1px solid #70b7fa;
+        background-color: rgba(114, 151, 179, 1);
+      }
+    }
+
+    .box_form {
+      padding-right: 44px;
+      margin-top: 38px;
+      height: 245px;
+      overflow: hidden;
+
+      .form_index {
+        font-size: 14px;
+        font-weight: bold;
+      }
+
+      .form_icon {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 0;
+        height: 0;
+        border-color: transparent #3be8ff;
+        border-width: 0 0 8px 8px;
+        border-style: solid;
+      }
+
+      .color {
+        color: #ff7734;
+      }
+
+      .color2 {
+        color: #57fff4;
+      }
+
+      .form_name {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: 14px;
+        color: #fff;
+        img {
+          margin-right: 8px;
+          width: 30px;
+          height: 30px;
+          border-radius: 50%;
+        }
+      }
+
+      .form_value {
+        color: #57fff4;
+        font-size: 18px;
+        font-weight: bold;
+      }
+    }
+  }
+}
+
+// 修改select背景颜色
+::v-deep(.el-input__wrapper) {
+  background: transparent;
+  background-color: rgba(48, 75, 95, 0.5);
+}
+
+// 修改select筛选框文字颜色
+::v-deep(.el-input__inner) {
+  color: #fff;
+}
+
+/*最外层透明*/
+.form ::v-deep(.el-table),
+.form ::v-deep(.el-table__expanded-cell) {
+  background-color: transparent;
+  color: white;
+  border: none;
+}
+/* 表格内背景颜色 */
+.form ::v-deep(.el-table th),
+.form ::v-deep(.el-table tr),
+.form ::v-deep(.el-table td),
+::v-deep(.el-table th.el-table__cell.is-leaf) {
+  background-color: transparent !important;
+  color: #5cb3ff;
+  font-size: 16px;
+  border-color: #021b29;
+  border-width: 3px;
+  height: 40px;
+}
+
+// 表格底部白线清除
+::v-deep(.el-table__inner-wrapper::before) {
+  height: 0;
+}
+
+// 修改表头背景颜色
+::v-deep(.el-table__header) {
+  background-color: rgba(58, 126, 199, 0.5);
+}
+// 清除表格默认padding
+::v-deep(.el-table .cell) {
+  padding: 0;
+}
+</style>

+ 629 - 0
src/components/user/userLeft.vue

@@ -0,0 +1,629 @@
+<template>
+  <div class="container">
+    <!-- 标题区域 -->
+    <div class="title">
+      <div class="title_text">考勤管理</div>
+      <div class="title_sub">Attendance Management</div>
+    </div>
+
+    <!-- 内容区域 -->
+    <div class="content">
+      <!-- 切换考勤区域 -->
+      <div class="btns">
+        <div
+          class="button"
+          :class="currentIndex === 0 ? 'active' : ''"
+          @click="handleChange(0)"
+        >
+          学生考勤
+        </div>
+        <div
+          class="button"
+          :class="currentIndex === 1 ? 'active' : ''"
+          @click="handleChange(1)"
+        >
+          教师考勤
+        </div>
+      </div>
+      <!-- 考勤统计区域 -->
+      <div class="sub_title">考勤统计</div>
+      <div class="stat_msg">
+        <div class="msg_change">
+          <div
+            class="change_box"
+            :class="currentTimeRang === 0 ? 'active' : ''"
+            @click="changeTimeRang(0)"
+          >
+            本学期
+          </div>
+          <div
+            class="change_box"
+            :class="currentTimeRang === 1 ? 'active' : ''"
+            @click="changeTimeRang(1)"
+          >
+            本周
+          </div>
+          <div
+            class="change_box"
+            :class="currentTimeRang === 2 ? 'active' : ''"
+            @click="changeTimeRang(2)"
+          >
+            本月
+          </div>
+        </div>
+
+        <div class="msg_detail">
+          <div class="detail_box">
+            <div class="box_num" ref="statDom">{{ statValue }}</div>
+            <div class="box_type">正常</div>
+          </div>
+          <div class="detail_box">
+            <div class="box_num" ref="statDom2">{{ statValue2 }}</div>
+            <div class="box_type">请假</div>
+          </div>
+          <div class="detail_box">
+            <div class="box_num" ref="statDom3">{{ statValue3 }}</div>
+            <div class="box_type">迟到</div>
+          </div>
+          <div class="detail_box">
+            <div class="box_num" ref="statDom4">{{ statValue4 }}</div>
+            <div class="box_type">超时打卡</div>
+          </div>
+          <div class="detail_box">
+            <div class="box_num" ref="statDom5">{{ statValue5 }}</div>
+            <div class="box_type">未打卡</div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 考勤记录区域 -->
+      <div class="sub_title">考勤记录</div>
+      <div class="record_msg">
+        <!-- 搜索框区域 -->
+        <div class="record_search">
+          <div class="search_input">
+            <input type="text" placeholder="请输入部门查询" />
+          </div>
+          <div class="search_btn">查询</div>
+        </div>
+
+        <!-- 筛选框区域 -->
+        <div class="record_select">
+          时间
+          <el-select
+            v-model="valueTime"
+            placeholder="请选择时间"
+            style="width: 150px"
+          >
+            <el-option
+              v-for="item in optionsTime"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+          类别
+          <el-select
+            v-model="valueType"
+            placeholder="请选择类别"
+            style="width: 150px"
+          >
+            <el-option
+              v-for="item in optionsType"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+
+        <!-- 表格区域 -->
+        <div class="record_form form">
+          <el-table :data="tableData">
+            <el-table-column
+              prop="class"
+              label="部门"
+              align="center"
+              width="60"
+            />
+            <el-table-column
+              prop="type"
+              label="考勤类别"
+              align="center"
+              width="60"
+            />
+            <el-table-column
+              prop="leave"
+              label="请假"
+              align="center"
+              width="50"
+            />
+            <el-table-column
+              prop="timeout"
+              label="超时打卡"
+              align="center"
+              width="60"
+            />
+            <el-table-column
+              prop="late"
+              label="迟到"
+              align="center"
+              width="50"
+            />
+            <el-table-column
+              prop="not"
+              label="未打卡"
+              align="center"
+              width="60"
+            />
+            <el-table-column
+              prop="normal"
+              label="正常"
+              align="center"
+              width="62"
+            />
+          </el-table>
+        </div>
+
+        <!-- 分页器区域 -->
+        <div class="pagination">
+          <el-pagination
+            v-model:current-page="currentPage"
+            v-model:page-size="pageSize"
+            layout="prev, pager, next, jumper"
+            :total="1000"
+            :pager-count="5"
+            small
+            background
+            @current-change="handleCurrentChange"
+          />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import { countUpNum } from "@/utils/countUpNum";
+
+const statValue = ref(50);
+const statValue2 = ref(50);
+const statValue3 = ref(50);
+const statValue4 = ref(50);
+const statValue5 = ref(50);
+
+const statDom = ref();
+const statDom2 = ref();
+const statDom3 = ref();
+const statDom4 = ref();
+const statDom5 = ref();
+
+// 切换学生教师考勤 0学生 1教师
+const currentIndex = ref(0);
+// 切换考勤统计时间 0本学期 1本周 2本月
+const currentTimeRang = ref(0);
+// 分页器当前页
+const currentPage = ref(1);
+// 每页多少条
+const pageSize = ref(8);
+// 时间筛选框绑定数据
+const valueTime = ref("");
+// 类别筛选框绑定数据
+const valueType = ref("");
+// 类别筛选框绑定数组
+const optionsType = [
+  {
+    value: "0",
+    label: "全部",
+  },
+  {
+    value: "1",
+    label: "请假",
+  },
+  {
+    value: "2",
+    label: "迟到",
+  },
+  {
+    value: "3",
+    label: "超时打卡",
+  },
+];
+// 时间筛选框绑定数组
+const optionsTime = [
+  {
+    value: "0",
+    label: "今日",
+  },
+  {
+    value: "1",
+    label: "本周",
+  },
+  {
+    value: "2",
+    label: "本月",
+  },
+];
+
+// 考勤记录表格数据
+const tableData = [
+  {
+    class: "七(15)班",
+    type: "123",
+    leave: "13",
+    timeout: "45",
+    late: "123",
+    not: "41",
+    normal: "246",
+  },
+  {
+    class: "七(5)班",
+    type: "123",
+    leave: "13",
+    timeout: "45",
+    late: "123",
+    not: "41",
+    normal: "246",
+  },
+  {
+    class: "七(5)班",
+    type: "123",
+    leave: "13",
+    timeout: "45",
+    late: "123",
+    not: "41",
+    normal: "246",
+  },
+  {
+    class: "七(5)班",
+    type: "123",
+    leave: "13",
+    timeout: "45",
+    late: "123",
+    not: "41",
+    normal: "246",
+  },
+  {
+    class: "七(5)班",
+    type: "123",
+    leave: "13",
+    timeout: "45",
+    late: "123",
+    not: "41",
+    normal: "246",
+  },
+  {
+    class: "七(5)班",
+    type: "123",
+    leave: "13",
+    timeout: "45",
+    late: "123",
+    not: "41",
+    normal: "246",
+  },
+  {
+    class: "七(5)班",
+    type: "123",
+    leave: "13",
+    timeout: "45",
+    late: "123",
+    not: "41",
+    normal: "246",
+  },
+  {
+    class: "七(5)班",
+    type: "123",
+    leave: "13",
+    timeout: "45",
+    late: "123",
+    not: "41",
+    normal: "246",
+  },
+];
+
+onMounted(() => {
+  getCountUpNum();
+});
+
+// 学生教师考勤切换按钮回调
+const handleChange = (value: number) => {
+  if (currentIndex.value !== value) {
+    if (value === 0) {
+      currentIndex.value = 0;
+    } else if (value === 1) {
+      currentIndex.value = 1;
+    }
+  }
+};
+
+// 考勤统计切换时间范围按钮回调
+const changeTimeRang = (value: number) => {
+  if (currentTimeRang.value !== value) {
+    if (value === 0) {
+      currentTimeRang.value = 0;
+    } else if (value === 1) {
+      currentTimeRang.value = 1;
+    } else if (value === 2) {
+      currentTimeRang.value = 2;
+    }
+  }
+};
+
+// 改变分页器当前页时的回调
+const handleCurrentChange = (value: number) => {
+  console.log(value);
+};
+
+const getCountUpNum = () => {
+  countUpNum(statDom.value, statValue.value);
+  countUpNum(statDom2.value, statValue2.value);
+  countUpNum(statDom3.value, statValue3.value);
+  countUpNum(statDom4.value, statValue4.value);
+  countUpNum(statDom5.value, statValue5.value);
+};
+</script>
+
+<style lang="scss" scoped>
+.container {
+  width: 435px;
+  height: 951px;
+  color: #fff;
+  background-image: url(@/assets/images/box-bg.png);
+
+  .title {
+    display: flex;
+    align-items: center;
+    width: 430px;
+    height: 47px;
+    font-family: "庞门正道标题体";
+    background-image: url(@/assets/images/title-bg.png);
+    background-size: 100% 100%;
+
+    .title_text {
+      margin-left: 38px;
+      font-size: 20px;
+      text-shadow: 0px 0px 9px #158eff;
+    }
+
+    .title_sub {
+      margin-left: 19px;
+      font-size: 12px;
+      color: #215a8e;
+    }
+  }
+
+  .content {
+    padding: 20px 19px 0 14px;
+    height: 904px;
+
+    .btns {
+      display: flex;
+      margin-top: 4px;
+      height: 58px;
+
+      .button {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        margin-right: 18px;
+        width: 88px;
+        height: 32px;
+        font-size: 14px;
+        border-radius: 4px;
+        border: 1px solid #bef4f7;
+        background-image: linear-gradient(
+          rgba(156, 255, 248, 0.4),
+          rgba(152, 217, 237, 0.15),
+          rgba(188, 216, 247, 0.4)
+        );
+        cursor: pointer;
+      }
+
+      .active {
+        background-image: linear-gradient(
+          rgba(29, 242, 228, 0.8),
+          rgba(61, 198, 239, 0.4),
+          rgba(26, 94, 232, 0.8)
+        );
+      }
+    }
+
+    .sub_title {
+      padding-left: 28px;
+      height: 35px;
+      line-height: 18px;
+      color: #abd6ff;
+      font-weight: bold;
+      background-image: url(@/assets/images/title-bg2.png);
+      background-size: 100% 100%;
+    }
+
+    .stat_msg {
+      height: 150px;
+      overflow: hidden;
+
+      .msg_change {
+        display: flex;
+        justify-content: space-between;
+        margin: 22px 0 22px 195px;
+        padding: 2px;
+        width: 200px;
+        height: 32px;
+        font-size: 14px;
+        border-radius: 2px;
+        border: 1px solid #9c9c9c;
+
+        .change_box {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 61px;
+          border-radius: 2px;
+          background-color: rgba(114, 151, 179, 0.3);
+          cursor: pointer;
+        }
+
+        .active {
+          border: 1px solid #70b7fa;
+          background-color: rgba(114, 151, 179, 1);
+        }
+      }
+
+      .msg_detail {
+        display: flex;
+        justify-content: space-evenly;
+        height: 74px;
+
+        .detail_box {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+
+          .box_num {
+            font-size: 20px;
+            font-family: "庞门正道标题体";
+          }
+
+          .box_type {
+            margin-top: 10px;
+            font-size: 12px;
+          }
+        }
+      }
+    }
+
+    .record_msg {
+      height: 600px;
+      overflow: hidden;
+
+      .record_search {
+        display: flex;
+        justify-content: space-between;
+        margin-top: 20px;
+        height: 32px;
+        font-size: 14px;
+
+        .search_input {
+          padding: 0 12px;
+          width: 330px;
+          height: 32px;
+          border-radius: 2px;
+          border: 1px solid #9c9c9c;
+          background-color: rgba(48, 75, 95, 0.5);
+
+          input {
+            width: 100%;
+            height: 100%;
+            color: #fff;
+            border: none;
+            outline: none;
+            background-color: transparent;
+          }
+        }
+
+        .search_btn {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 58px;
+          height: 32px;
+          background-image: linear-gradient(
+            rgba(29, 242, 228, 0.8),
+            rgba(61, 198, 239, 0.4),
+            rgba(26, 94, 232, 0.8)
+          );
+          cursor: pointer;
+        }
+      }
+
+      .record_select {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-top: 20px;
+        height: 32px;
+        font-size: 14px;
+      }
+
+      .record_form {
+        margin-top: 22px;
+        height: 370px;
+      }
+
+      .pagination {
+        display: flex;
+        justify-content: flex-end;
+        margin-top: 25px;
+      }
+    }
+  }
+}
+
+// 修改select背景颜色
+::v-deep(.el-input__wrapper) {
+  background: transparent;
+  background-color: rgba(48, 75, 95, 0.5);
+}
+
+// 修改select筛选框文字颜色
+::v-deep(.el-input__inner) {
+  color: #fff;
+}
+
+/*最外层透明*/
+.form ::v-deep(.el-table),
+.form ::v-deep(.el-table__expanded-cell) {
+  background-color: transparent;
+  color: white;
+  border: none;
+}
+/* 表格内背景颜色 */
+.form ::v-deep(.el-table th),
+.form ::v-deep(.el-table tr),
+.form ::v-deep(.el-table td),
+::v-deep(.el-table th.el-table__cell.is-leaf) {
+  background-color: transparent !important;
+  color: white;
+  font-size: 12px;
+  border-color: rgba(255, 255, 255, 0.1);
+  height: 40px;
+}
+
+// 表格底部白线清除
+::v-deep(.el-table__inner-wrapper::before) {
+  height: 0;
+}
+
+// 修改表头背景颜色
+::v-deep(.el-table__header) {
+  background-color: rgba(58, 126, 199, 0.5);
+}
+// 清除表格默认padding
+::v-deep(.el-table .cell) {
+  padding: 0;
+}
+
+// 修改分页器未选中时的样式
+::v-deep(.el-pagination.is-background .btn-next),
+::v-deep(.el-pagination.is-background .btn-prev),
+::v-deep(.el-pagination.is-background .el-pager li:not(.disabled)) {
+  margin: 0 1px;
+  background-color: transparent;
+  border: 1px solid #707070;
+  color: #fff;
+}
+
+// 修改分页器选中时的样式
+::v-deep(
+    .el-pagination.is-background .el-pager li:not(.is-disabled).is-active
+  ) {
+  color: #70b7fa;
+  border: 1px solid #70b7fa;
+}
+
+::v-deep(.el-pagination__goto),
+::v-deep(.el-pagination__classifier) {
+  color: #fff;
+}
+</style>

+ 568 - 0
src/components/user/userRight.vue

@@ -0,0 +1,568 @@
+<template>
+  <div class="container">
+    <!-- 标题区域 -->
+    <div class="title">
+      <div class="title_text">用户汇总</div>
+      <div class="title_sub">User Summary</div>
+    </div>
+
+    <!-- 内容区域 -->
+    <div class="content">
+      <!-- 学生汇总区域 -->
+      <div class="sub_title">
+        <div>
+          学生汇总
+          <span
+            >/<span ref="totalStudentDom">{{ totalStudent }}</span
+            >人</span
+          >
+        </div>
+        <div class="more" @click="handleClickMore(0)">查看更多 ></div>
+      </div>
+      <!-- 学生汇总表格区域 -->
+      <div class="student form">
+        <el-table :data="tableData">
+          <el-table-column
+            prop="name"
+            label="姓名"
+            align="center"
+            width="130"
+            show-overflow-tooltip
+          />
+          <el-table-column prop="num" label="编号" align="center" width="60" />
+          <el-table-column
+            prop="class"
+            label="部门"
+            align="center"
+            width="150"
+          />
+          <el-table-column label="操作" align="center" width="60">
+            <template #default="{ row }">
+              <div class="check" @click="handleCheckMsg(0)">查看</div>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <!-- 教师汇总区域 -->
+      <div class="sub_title">
+        <div>
+          教师汇总
+          <span
+            >/<span ref="totalTeacherDom">{{ totalTeacher }}</span
+            >人</span
+          >
+        </div>
+        <div class="more" @click="handleClickMore(1)">查看更多 ></div>
+      </div>
+      <!-- 教师汇总表格区域 -->
+      <div class="teacher form">
+        <el-table :data="tableData">
+          <el-table-column
+            prop="name"
+            label="姓名"
+            align="center"
+            width="130"
+            show-overflow-tooltip
+          />
+          <el-table-column prop="num" label="编号" align="center" width="60" />
+          <el-table-column
+            prop="class"
+            label="部门"
+            align="center"
+            width="150"
+          />
+          <el-table-column label="操作" align="center" width="60">
+            <template #default="{ row }">
+              <div class="check" @click="handleCheckMsg(1)">查看</div>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+
+    <!-- 查看更多弹窗区域 -->
+    <div v-if="showPop" class="popup">
+      <div class="pop_box">
+        <!-- 标题区域 -->
+        <div class="pop_title">
+          <span>{{ popType === 0 ? "学生" : "教师" }}汇总</span>
+        </div>
+
+        <!-- 关闭按钮区域 -->
+        <div class="pop_close" @click="handleClose">×</div>
+
+        <!-- 内容区域 -->
+        <div class="pop_content">
+          <!-- 搜索区域 -->
+          <div class="pop_search">
+            <div class="search_input">
+              <input type="text" placeholder="请输入关键字" />
+            </div>
+            <div class="search_btn">查询</div>
+          </div>
+
+          <!-- 表格区域 -->
+          <div class="pop_form form">
+            <el-table :data="tableDataPop">
+              <el-table-column label="序号" align="center" width="80">
+                <template #default="{ $index }">
+                  <div>{{ ($index + 1).toString().padStart(2, 0) }}</div>
+                </template>
+              </el-table-column>
+
+              <el-table-column prop="name" label="姓名" align="center" />
+              <el-table-column prop="num" label="编号" align="center" />
+              <el-table-column prop="class" label="部门" align="center" />
+
+              <el-table-column label="操作" align="center">
+                <template #default="{ row }">
+                  <div class="pop_check" @click="clickPopCheck">查看</div>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+          <!-- 分页器区域 -->
+          <div class="pop_pagination">
+            <el-pagination
+              v-model:current-page="currentPage"
+              v-model:page-size="pageSize"
+              small
+              background
+              layout="prev, pager, next, jumper"
+              :total="1000"
+              :pager-count="5"
+              @current-change="handleCurrentChange"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import { useRouter } from "vue-router";
+import { countUpNum } from "@/utils/countUpNum";
+
+const totalStudent = ref(4500);
+const totalTeacher = ref(4500);
+
+const totalStudentDom = ref(4500);
+const totalTeacherDom = ref(4500);
+
+// 是否展示弹窗
+const showPop = ref(false);
+
+// 分页器当前页
+const currentPage = ref(1);
+
+// 每页多少条数据
+const pageSize = ref(10);
+
+// 弹窗类型 0为学生弹窗 1为教师弹窗
+const popType = ref(0);
+
+// 表格数据
+const tableData = [
+  {
+    name: "张三张三张三",
+    num: "123",
+    class: "七(5)班",
+  },
+  {
+    name: "张三",
+    num: "123",
+    class: "七(5)班",
+  },
+  {
+    name: "张三",
+    num: "123",
+    class: "七(5)班",
+  },
+  {
+    name: "张三",
+    num: "123",
+    class: "七(5)班",
+  },
+  {
+    name: "张三",
+    num: "123",
+    class: "七(5)班",
+  },
+  {
+    name: "张三",
+    num: "123",
+    class: "七(5)班",
+  },
+  {
+    name: "张三",
+    num: "123",
+    class: "七(5)班",
+  },
+  {
+    name: "张三",
+    num: "123",
+    class: "七(5)班",
+  },
+  {
+    name: "张三",
+    num: "123",
+    class: "七(5)班",
+  },
+  {
+    name: "张三",
+    num: "123",
+    class: "七(5)班",
+  },
+];
+
+// 弹窗表格数据
+const tableDataPop = [
+  {
+    name: "张三",
+    num: "2051",
+    class: "七四班",
+  },
+  {
+    name: "李四",
+    num: "2051",
+    class: "七四班",
+  },
+  {
+    name: "王五",
+    num: "2051",
+    class: "七四班",
+  },
+  {
+    name: "老六",
+    num: "2051",
+    class: "七四班",
+  },
+  {
+    name: "张三",
+    num: "2051",
+    class: "七四班",
+  },
+  {
+    name: "李四",
+    num: "2051",
+    class: "七四班",
+  },
+  {
+    name: "王五",
+    num: "2051",
+    class: "七四班",
+  },
+  {
+    name: "老六",
+    num: "2051",
+    class: "七四班",
+  },
+  {
+    name: "张三",
+    num: "2051",
+    class: "七四班",
+  },
+  {
+    name: "李四二",
+    num: "2051",
+    class: "七四班",
+  },
+];
+
+const router = useRouter();
+
+onMounted(() => {
+  countUpNum(totalStudentDom.value, totalStudent.value);
+  countUpNum(totalTeacherDom.value, totalTeacher.value);
+});
+
+// 点击表格查看按钮回调 0学生 1教师
+const handleCheckMsg = (value: number) => {
+  if (value === 0) {
+    router.push("student");
+  } else {
+    router.push("teacher");
+  }
+};
+
+// 点击查看更多按钮回调 0学生 1教师
+const handleClickMore = (value: number) => {
+  showPop.value = true;
+  popType.value = value;
+};
+
+// 分页器当前页修改时的回调
+const handleCurrentChange = (value: number) => {
+  console.log(value);
+};
+
+// 点击弹窗关闭按钮回调
+const handleClose = () => {
+  showPop.value = false;
+};
+
+// 弹窗表格查看按钮回调
+const clickPopCheck = () => {
+  // showPop.value = false;
+  handleCheckMsg(popType.value);
+};
+</script>
+
+<style lang="scss" scoped>
+.container {
+  width: 435px;
+  height: 951px;
+  color: #fff;
+  background-image: url(@/assets/images/box-bg.png);
+
+  .title {
+    display: flex;
+    align-items: center;
+    width: 430px;
+    height: 47px;
+    font-family: "庞门正道标题体";
+    background-image: url(@/assets/images/title-bg.png);
+    background-size: 100% 100%;
+
+    .title_text {
+      margin-left: 38px;
+      font-size: 20px;
+      text-shadow: 0px 0px 9px #158eff;
+    }
+
+    .title_sub {
+      margin-left: 19px;
+      font-size: 12px;
+      color: #215a8e;
+    }
+  }
+
+  .content {
+    padding: 20px 19px 0 14px;
+    height: 904px;
+
+    .sub_title {
+      display: flex;
+      justify-content: space-between;
+      padding-left: 28px;
+      height: 35px;
+      line-height: 18px;
+      color: #abd6ff;
+      font-weight: bold;
+      background-image: url(@/assets/images/title-bg2.png);
+      background-size: 100% 100%;
+
+      span {
+        margin-left: 5px;
+        font-size: 12px;
+        font-weight: 400;
+      }
+
+      .more {
+        font-weight: 400;
+        font-size: 12px;
+        cursor: pointer;
+      }
+    }
+
+    .student {
+      margin: 22px 0;
+      height: 360px;
+      overflow: hidden;
+
+      .check {
+        color: #ede27b;
+        cursor: pointer;
+      }
+    }
+
+    .teacher {
+      padding-top: 20px;
+      height: 390px;
+      overflow: hidden;
+
+      .check {
+        color: #ede27b;
+        cursor: pointer;
+      }
+    }
+  }
+
+  .popup {
+    z-index: 999;
+    position: absolute;
+    top: -83px;
+    left: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 1920px;
+    height: 1080px;
+    background-color: rgba(0, 0, 0, 0.6);
+
+    .pop_box {
+      position: relative;
+      padding: 15px 47px 0 24px;
+      width: 585px;
+      height: 646px;
+      border-radius: 16px;
+      border: 1px solid #8c8c8c;
+      background-color: rgba(31, 58, 87, 0.8);
+
+      .pop_title {
+        height: 34px;
+        background-image: url(@/assets/images/pop-title-bg.png);
+        background-size: 100% 100%;
+
+        span {
+          margin-left: 24px;
+          color: #bbe2fe;
+        }
+      }
+
+      .pop_close {
+        position: absolute;
+        top: 14px;
+        right: 48px;
+        width: 24px;
+        height: 24px;
+        line-height: 18px;
+        text-align: center;
+        color: #a6a6a6;
+        font-size: 25px;
+        border-radius: 2px;
+        border: 1px solid #a6a6a6;
+        cursor: pointer;
+      }
+      .pop_content {
+        padding: 37px 0 0 17px;
+        height: 580px;
+        overflow: hidden;
+
+        .pop_search {
+          display: flex;
+          justify-content: space-between;
+          height: 32px;
+          .search_input {
+            padding: 0 10px;
+            width: 430px;
+            border-radius: 2px;
+            border: 1px solid #9c9c9c;
+            background-color: rgba(48, 75, 95, 0.5);
+
+            input {
+              width: 100%;
+              height: 100%;
+              background-color: transparent;
+              border: none;
+              outline: none;
+              color: #fff;
+              font-size: 14px;
+            }
+          }
+
+          .search_btn {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            width: 58px;
+            font-size: 14px;
+            background-image: linear-gradient(
+              rgba(29, 242, 228, 1),
+              rgba(61, 198, 239, 0.4),
+              rgba(26, 94, 232, 1)
+            );
+            cursor: pointer;
+          }
+        }
+
+        .pop_form {
+          margin-top: 22px;
+          height: 445px;
+          overflow: hidden;
+
+          .pop_check {
+            color: #ede27b;
+            cursor: pointer;
+          }
+        }
+
+        .pop_pagination {
+          display: flex;
+          align-items: center;
+          justify-content: flex-end;
+          margin-top: 10px;
+          height: 35px;
+        }
+      }
+    }
+  }
+}
+
+/*最外层透明*/
+.form ::v-deep(.el-table),
+.form ::v-deep(.el-table__expanded-cell) {
+  background-color: transparent;
+  color: white;
+  border: none;
+}
+/* 表格内背景颜色 */
+.form ::v-deep(.el-table th),
+.form ::v-deep(.el-table tr),
+.form ::v-deep(.el-table td),
+::v-deep(.el-table th.el-table__cell.is-leaf) {
+  background-color: transparent !important;
+  color: white;
+  font-size: 12px;
+  border-color: rgba(255, 255, 255, 0.1);
+  height: 40px;
+}
+
+// 表格底部白线清除
+::v-deep(.el-table__inner-wrapper::before) {
+  height: 0;
+}
+
+// 修改表头背景颜色
+::v-deep(.el-table__header) {
+  background-color: rgba(58, 126, 199, 0.5);
+}
+// 清除表格默认padding
+::v-deep(.el-table .cell) {
+  padding: 0;
+}
+
+// 修改分页器未选中时的样式
+::v-deep(.el-pagination.is-background .btn-next),
+::v-deep(.el-pagination.is-background .btn-prev),
+::v-deep(.el-pagination.is-background .el-pager li:not(.disabled)) {
+  margin: 0 1px;
+  background-color: transparent;
+  border: 1px solid #707070;
+  color: #fff;
+}
+
+// 修改分页器选中时的样式
+::v-deep(
+    .el-pagination.is-background .el-pager li:not(.is-disabled).is-active
+  ) {
+  color: #70b7fa;
+  border: 1px solid #70b7fa;
+}
+
+::v-deep(.el-pagination__goto),
+::v-deep(.el-pagination__classifier) {
+  color: #fff;
+}
+
+::v-deep(.el-input__wrapper) {
+  background: transparent;
+  background-color: rgba(48, 75, 95, 0.5);
+}
+
+::v-deep(.el-input__inner) {
+  color: #fff;
+}
+</style>

+ 27 - 0
src/main.ts

@@ -0,0 +1,27 @@
+import '@/assets/main.css'
+
+import { createApp } from 'vue'
+import App from './App.vue'
+import router from './router'
+
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+
+// element组件默认语言设置为中文
+import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
+
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+
+const app = createApp(App)
+
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+    app.component(key, component)
+  }
+
+app.use(ElementPlus, {
+  locale: zhCn
+})
+
+app.use(router)
+
+app.mount('#app')

+ 45 - 0
src/router/index.ts

@@ -0,0 +1,45 @@
+import { createRouter, createWebHistory } from 'vue-router'
+
+const router = createRouter({
+  history: createWebHistory(import.meta.env.BASE_URL),
+  routes: [
+    {
+      path: '/',
+      name: 'school',
+      component: () => import('@/views/school.vue'),
+      meta:{showRouter:true}
+    },
+    {
+      path: '/user',
+      name: 'user',
+      component: () => import('@/views/user.vue'),
+      meta:{showRouter:true}
+    },
+    {
+      path: '/energy',
+      name: 'energy',
+      component: () => import('@/views/energy.vue'),
+      meta:{showRouter:true}
+    },
+    {
+      path: '/more',
+      name: 'more',
+      component: () => import('@/views/more.vue'),
+      meta:{showRouter:false}
+    },
+    {
+      path: '/teacher',
+      name: 'teacher',
+      component: () => import('@/views/teacher.vue'),
+      meta:{showRouter:false}
+    },
+    {
+      path: '/student',
+      name: 'student',
+      component: () => import('@/views/student.vue'),
+      meta:{showRouter:false}
+    }
+  ]
+})
+
+export default router

+ 11 - 0
src/utils/countUpNum.js

@@ -0,0 +1,11 @@
+import { CountUp } from "countup.js";
+
+export const countUpNum = (dom, value, decimalPlaces = 0, durations = 2) => {
+  const options = {
+    // 小数点位
+    decimalPlaces,
+    // 持续时间
+    durations,
+  };
+  new CountUp(dom, value, options).start();
+};

+ 30 - 0
src/utils/getTime.ts

@@ -0,0 +1,30 @@
+// 获取当前时间
+export const getCurrentTime = () => {
+  let date = new Date();
+  // 年份
+  let Y = date.getFullYear();
+  // 月份
+  let M = (date.getMonth() + 1).toString().padStart(2, 0);
+  // 日期
+  let D = date.getDate().toString().padStart(2, 0);
+  
+
+  //   小时
+  let H = date.getHours().toString().padStart(2, 0);
+  // 分钟
+  let MM = date.getMinutes().toString().padStart(2, 0);
+
+  // 秒
+  let S = date.getSeconds().toString().padStart(2, 0);
+
+  // 当前日期
+  let currentDate = Y + "-" + M + "-" + D 
+
+  // 当前时分秒
+  let currentTime = " " + H + ":" + MM + ":" + S;
+
+  return {
+    currentDate,
+    currentTime,
+  };
+};

+ 24 - 0
src/views/energy.vue

@@ -0,0 +1,24 @@
+<template>
+  <div class="energy">
+    <!-- 左边区域 -->
+    <EnergyLeft />
+
+    <!-- 右边区域 -->
+    <EnergyRight />
+  </div>
+</template>
+
+<script setup lang="ts">
+import EnergyLeft from "@/components/energy/energyLeft.vue";
+import EnergyRight from "@/components/energy/energyRight.vue";
+</script>
+
+<style scoped>
+.energy {
+  position: relative;
+  display: flex;
+  justify-content: space-between;
+  padding: 25px 17px 0 17px;
+  height: 997px;
+}
+</style>

+ 196 - 0
src/views/more.vue

@@ -0,0 +1,196 @@
+<template>
+  <div class="more">
+    <!-- 标题区域 -->
+    <div class="title"></div>
+
+    <!-- 监控内容区域 -->
+    <div class="content">
+      <!-- 选择监控位置区域 -->
+      <div class="content_left">
+        <el-tree
+          :data="dataSource"
+          node-key="id"
+          highlight-current
+          accordion
+          @node-click="handleNodeClick"
+        >
+          <template #default="{ node, data }">
+            <div class="left_tree">
+              <component
+                :is="node.level === 1 ? 'FolderOpened' : ''"
+                style="width: 20px; height: 20px; color: #70b7fa"
+              />
+              <div class="tree_text">{{ node.label }}</div>
+            </div>
+          </template>
+        </el-tree>
+      </div>
+      <!-- 监控展示区域 -->
+      <div class="content_right">
+        <img
+          src="https://materials.cdn.bcebos.com/images/107505592/68889f5dbf4bd8d8ef0cd11f98b5adea.jpeg"
+        />
+      </div>
+    </div>
+
+    <!-- 关闭按钮区域 -->
+    <div class="close" @click="handleClose">×</div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from "vue";
+import { useRouter } from "vue-router";
+
+const router = useRouter();
+
+// 树状结构数据
+const dataSource = ref([
+  {
+    id: 1,
+    label: "七年级",
+    icon: "FolderOpened",
+    children: [
+      {
+        id: 11,
+        label: "1班",
+      },
+      {
+        id: "1-2",
+        label: "2班",
+      },
+      {
+        id: "1-3",
+        label: "3班",
+      },
+      {
+        id: "1-4",
+        label: "4班",
+      },
+    ],
+  },
+  {
+    id: 2,
+    label: "八年级",
+    icon: "FolderOpened",
+    children: [
+      {
+        id: "2-1",
+        label: "1班",
+      },
+      {
+        id: "2-2",
+        label: "2班",
+      },
+      {
+        id: "2-3",
+        label: "3班",
+      },
+      {
+        id: "2-4",
+        label: "4班",
+      },
+    ],
+  },
+]);
+
+// 点击树状结构每一个节点触发的回调
+const handleNodeClick = (data: any) => {
+  console.log(data);
+};
+
+// 点击关闭按钮回调
+const handleClose = () => {
+  router.push("/");
+};
+</script>
+
+<style lang="scss" scoped>
+.more {
+  z-index: 2;
+  position: relative;
+  margin-top: 39px;
+  margin-left: 17px;
+  padding: 22px 38px 33px 27px;
+  width: 1887px;
+  height: 942px;
+  background-color: rgba(2, 27, 41, 0.9);
+
+  .title {
+    width: 1823px;
+    height: 32px;
+    background-image: url(@/assets/images/more-title-bg.png);
+    background-size: 100% 100%;
+  }
+
+  .content {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 30px;
+    height: 824px;
+
+    .content_left {
+      width: 108px;
+      height: 100%;
+
+      .left_tree {
+        display: flex;
+        align-items: center;
+        width: 100%;
+        font-size: 14px;
+        font-weight: bold;
+        font-size: 14px;
+
+        .tree_text {
+          margin-left: 6px;
+          font-size: 12px;
+          font-weight: 400;
+        }
+      }
+    }
+
+    .content_right {
+      width: 1689px;
+      height: 100%;
+
+      img {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+    }
+  }
+
+  .close {
+    position: absolute;
+    top: 18px;
+    right: 38px;
+    width: 24px;
+    height: 24px;
+    line-height: 18px;
+    text-align: center;
+    color: #a6a6a6;
+    font-size: 25px;
+    border-radius: 2px;
+    border: 1px solid #a6a6a6;
+    cursor: pointer;
+  }
+}
+
+// 修改树形组件样式
+::v-deep(.el-tree) {
+  color: #fff;
+  background: transparent;
+}
+
+::v-deep(
+    .el-tree--highlight-current
+      .el-tree-node.is-current
+      > .el-tree-node__content
+  ),
+::v-deep(.el-tree-node__content:hover),
+::v-deep(.el-tree-node:focus > .el-tree-node__content) {
+  background-color: rgba(112, 183, 250, 0.2);
+  color: #70b7fa;
+}
+</style>

+ 24 - 0
src/views/school.vue

@@ -0,0 +1,24 @@
+<template>
+  <div class="school">
+    <!-- 左边区域 -->
+    <SchoolLeft />
+
+    <!-- 右边区域 -->
+    <SchoolRight />
+  </div>
+</template>
+
+<script setup lang="ts">
+import SchoolLeft from "@/components/school/schoolLeft.vue";
+import SchoolRight from "@/components/school/schoolRight.vue";
+</script>
+
+<style lang="scss" scoped>
+.school {
+  position: relative;
+  display: flex;
+  justify-content: space-between;
+  padding: 25px 17px 0 17px;
+  height: 997px;
+}
+</style>

+ 72 - 0
src/views/student.vue

@@ -0,0 +1,72 @@
+<template>
+  <div class="student">
+    <!-- 标题区域 -->
+    <div class="title"></div>
+
+    <!-- 主体内容区域 -->
+    <div class="content">
+      <StudentLeft />
+      <StudentCenter />
+      <StudentRight />
+    </div>
+
+    <!-- 关闭按钮区域 -->
+    <div class="close" @click="handleClose">×</div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useRouter } from "vue-router";
+import StudentLeft from "@/components/student/studentLeft.vue";
+import StudentCenter from "@/components/student/studentCenter.vue";
+import StudentRight from "@/components/student/studentRight.vue";
+
+const router = useRouter();
+
+// 点击关闭按钮回调
+const handleClose = () => {
+  router.push("user");
+};
+</script>
+
+<style lang="scss" scoped>
+.student {
+  z-index: 2;
+  position: relative;
+  margin-top: 39px;
+  margin-left: 17px;
+  padding: 22px 38px 33px 27px;
+  width: 1887px;
+  height: 942px;
+  background-color: rgba(2, 27, 41, 0.9);
+
+  .title {
+    width: 1823px;
+    height: 32px;
+    background-image: url(@/assets/images/student-title-bg.png);
+    background-size: 100% 100%;
+  }
+
+  .content {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 30px;
+    height: 834px;
+  }
+
+  .close {
+    position: absolute;
+    top: 18px;
+    right: 38px;
+    width: 24px;
+    height: 24px;
+    line-height: 18px;
+    text-align: center;
+    color: #a6a6a6;
+    font-size: 25px;
+    border-radius: 2px;
+    border: 1px solid #a6a6a6;
+    cursor: pointer;
+  }
+}
+</style>

+ 72 - 0
src/views/teacher.vue

@@ -0,0 +1,72 @@
+<template>
+  <div class="teacher">
+    <!-- 标题区域 -->
+    <div class="title"></div>
+
+    <!-- 主体内容区域 -->
+    <div class="content">
+      <TeacherLeft />
+      <TeacherCenter />
+      <TeacherRight />
+    </div>
+
+    <!-- 关闭按钮区域 -->
+    <div class="close" @click="handleClose">×</div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useRouter } from "vue-router";
+import TeacherLeft from "@/components/teacher/teacherLeft.vue";
+import TeacherCenter from "@/components/teacher/teacherCenter.vue";
+import TeacherRight from "@/components/teacher/teacherRight.vue";
+
+const router = useRouter();
+
+// 点击关闭按钮回调
+const handleClose = () => {
+  router.push("user");
+};
+</script>
+
+<style lang="scss" scoped>
+.teacher {
+  z-index: 2;
+  position: relative;
+  margin-top: 39px;
+  margin-left: 17px;
+  padding: 22px 38px 33px 27px;
+  width: 1887px;
+  height: 942px;
+  background-color: rgba(2, 27, 41, 0.9);
+
+  .title {
+    width: 1823px;
+    height: 32px;
+    background-image: url(@/assets/images/teacher-title-bg.png);
+    background-size: 100% 100%;
+  }
+
+  .content {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 30px;
+    height: 824px;
+  }
+
+  .close {
+    position: absolute;
+    top: 18px;
+    right: 38px;
+    width: 24px;
+    height: 24px;
+    line-height: 18px;
+    text-align: center;
+    color: #a6a6a6;
+    font-size: 25px;
+    border-radius: 2px;
+    border: 1px solid #a6a6a6;
+    cursor: pointer;
+  }
+}
+</style>

+ 24 - 0
src/views/user.vue

@@ -0,0 +1,24 @@
+<template>
+  <div class="user">
+    <!-- 左边区域 -->
+    <UserLeft />
+
+    <!-- 右边区域 -->
+    <UserRight />
+  </div>
+</template>
+
+<script setup lang="ts">
+import UserLeft from "@/components/user/userLeft.vue";
+import UserRight from "@/components/user/userRight.vue";
+</script>
+
+<style scoped>
+.user {
+  position: relative;
+  display: flex;
+  justify-content: space-between;
+  padding: 25px 17px 0 17px;
+  height: 997px;
+}
+</style>

+ 13 - 0
tsconfig.app.json

@@ -0,0 +1,13 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.dom.json",
+  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
+  "exclude": ["src/**/__tests__/*"],
+  "compilerOptions": {
+    "composite": true,
+    "noEmit": true,
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  }
+}

+ 11 - 0
tsconfig.json

@@ -0,0 +1,11 @@
+{
+  "files": [],
+  "references": [
+    {
+      "path": "./tsconfig.node.json"
+    },
+    {
+      "path": "./tsconfig.app.json"
+    }
+  ]
+}

+ 17 - 0
tsconfig.node.json

@@ -0,0 +1,17 @@
+{
+  "extends": "@tsconfig/node18/tsconfig.json",
+  "include": [
+    "vite.config.*",
+    "vitest.config.*",
+    "cypress.config.*",
+    "nightwatch.conf.*",
+    "playwright.config.*"
+  ],
+  "compilerOptions": {
+    "composite": true,
+    "noEmit": true,
+    "module": "ESNext",
+    "moduleResolution": "Bundler",
+    "types": ["node"]
+  }
+}

+ 16 - 0
vite.config.ts

@@ -0,0 +1,16 @@
+import { fileURLToPath, URL } from 'node:url'
+
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [
+    vue(),
+  ],
+  resolve: {
+    alias: {
+      '@': fileURLToPath(new URL('./src', import.meta.url))
+    }
+  }
+})