From ca9a92cfc2f2bd8c724020ee5e46094633eb80a5 Mon Sep 17 00:00:00 2001
From: 黄何裕 <1053952480@qq.com>
Date: 星期五, 12 七月 2024 17:39:45 +0800
Subject: [PATCH] 处理冲突

---
 src/components/NormalHeader/index.vue          |    4 
 src/views/personal-center/index.vue            |  195 +++++++++++++++++
 src/views/home/components/user-panel/index.vue |  228 ++++++++++----------
 components.d.ts                                |    4 
 src/api/modules/personalCenter.js              |    9 
 auto-imports.d.ts                              |    2 
 vite.config.js                                 |    2 
 src/router/index.js                            |    5 
 src/views/exam/index.vue                       |  175 ++++++++++-----
 9 files changed, 439 insertions(+), 185 deletions(-)

diff --git a/auto-imports.d.ts b/auto-imports.d.ts
index 1d89ee8..78813d8 100644
--- a/auto-imports.d.ts
+++ b/auto-imports.d.ts
@@ -5,5 +5,5 @@
 // Generated by unplugin-auto-import
 export {}
 declare global {
-
+  const ElMessage: typeof import('element-plus/es')['ElMessage']
 }
diff --git a/components.d.ts b/components.d.ts
index a89ff70..1f66d7d 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -7,12 +7,14 @@
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElCollapse: typeof import('element-plus/es')['ElCollapse']
     ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
     ElCountdown: typeof import('element-plus/es')['ElCountdown']
+    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
     ElDialog: typeof import('element-plus/es')['ElDialog']
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
@@ -32,6 +34,8 @@
     ElSlider: typeof import('element-plus/es')['ElSlider']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+    ElTabPane: typeof import('element-plus/es')['ElTabPane']
+    ElTabs: typeof import('element-plus/es')['ElTabs']
     ElTag: typeof import('element-plus/es')['ElTag']
     ExamAudio: typeof import('./src/components/ExamAudio/index.vue')['default']
     ExamInfo: typeof import('./src/components/ExamInfo/index.vue')['default']
diff --git a/src/api/modules/personalCenter.js b/src/api/modules/personalCenter.js
new file mode 100644
index 0000000..6380f6e
--- /dev/null
+++ b/src/api/modules/personalCenter.js
@@ -0,0 +1,9 @@
+import service from "@/api";
+
+export const uploadImg = (fromData) => {
+  return service.post("/api/student/upload/image", fromData, {
+    headers: {
+      "Content-Type": "multipart/form-data",
+    },
+  });
+};
diff --git a/src/components/NormalHeader/index.vue b/src/components/NormalHeader/index.vue
index c70f50e..dd22149 100644
--- a/src/components/NormalHeader/index.vue
+++ b/src/components/NormalHeader/index.vue
@@ -9,8 +9,8 @@
 
     <div class="user-container flex items-center">
       <div class="avatar-container w-12 h-12 rounded-full overflow-hidden mr-3">
-        <div class="avatar-content" v-if="userInfo.imagePath"">
-          <img :src="userInfo.imagePath" class="avatar-img" alt="">
+        <div class="avatar-content" v-if="userInfo.imagePath">
+          <img :src="'/api/files/'+userInfo.imagePath" class="avatar-img" alt="">
         </div>
         <div class="avatar-content" :style="{ backgroundColor: getColor }">
           <div class="name text-xl font-bold text-white">{{ getFirstName }}</div>
diff --git a/src/router/index.js b/src/router/index.js
index 24bd96d..a327d8e 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -66,6 +66,11 @@
     path: '/folder',
     component: () => import('@/views/folder/index.vue'),
   },
+  //涓汉涓績
+  {
+    path: '/personal-center',
+    component: () => import('@/views/personal-center/index.vue'),
+  }
 ];
 
 const router = createRouter({
diff --git a/src/views/exam/index.vue b/src/views/exam/index.vue
index bd858ed..dbbf30c 100644
--- a/src/views/exam/index.vue
+++ b/src/views/exam/index.vue
@@ -1,5 +1,7 @@
 <template>
-  <div class="exam-container w-screen h-screen bg-slate-50 relative overflow-hidden">
+  <div
+    class="exam-container w-screen h-screen bg-slate-50 relative overflow-hidden"
+  >
     <div class="top-bg bg-blue-500"></div>
     <div class="exam-content">
       <div class="exam-wrapper container mx-auto h-full flex flex-col">
@@ -24,8 +26,10 @@
           <!-- 绛旈鍗″尯 -->
           <div class="answer-wrapper answer-left mr-8 shadow-xl p-4 box-border">
             <div class="wrapper h-full flex flex-col items-center">
-              <div class="title-wrapper w-full flex justify-between items-center mb-5">
-                <div class="title text-xl font-semibold ">绛旈鍗�</div>
+              <div
+                class="title-wrapper w-full flex justify-between items-center mb-5"
+              >
+                <div class="title text-xl font-semibold">绛旈鍗�</div>
                 <AnswerTag></AnswerTag>
               </div>
 
@@ -40,9 +44,13 @@
               </div>
 
               <div class="submit-wrapper">
-                <el-button type="primary" class="submit-button" @click="submitExamHandle">鎻愪氦璇曞嵎</el-button>
+                <el-button
+                  type="primary"
+                  class="submit-button"
+                  @click="submitExamHandle"
+                  >鎻愪氦璇曞嵎</el-button
+                >
               </div>
-
             </div>
           </div>
 
@@ -50,15 +58,20 @@
           <div class="answer-wrapper answer-right grow shadow-xl p-4">
             <div class="wrapper h-full flex flex-col">
               <div class="title-wrapper w-full flex mb-5">
-                <div class="title text-xl font-semibold ">{{ examType[currentType] }} ({{
-                  examStore.getActiveQuestion.questionScore }}鍒�)
+                <div class="title text-xl font-semibold">
+                  {{ examType[currentType] }} ({{
+                    examStore.getActiveQuestion.questionScore
+                  }}鍒�)
                 </div>
               </div>
 
               <div class="main-wrapper w-full grow relative my-5">
                 <div class="main-content absolute top-0 bottom-0 w-full">
                   <Transition appear name="fade-transform" mode="out-in">
-                    <component :is="typeComponent[currentType]" :key="currentIndex"></component>
+                    <component
+                      :is="typeComponent[currentType]"
+                      :key="currentIndex"
+                    ></component>
                   </Transition>
                 </div>
               </div>
@@ -66,10 +79,17 @@
               <div class="tool-wrapper flex justify-end">
                 <div class="button-container flex items-center">
                   <div class="button-item">
-                    <el-button class="tool-button" @click="prevQuestion">涓婁竴棰�</el-button>
+                    <el-button class="tool-button" @click="prevQuestion"
+                      >涓婁竴棰�</el-button
+                    >
                   </div>
                   <div class="button-item">
-                    <el-button class="tool-button" type="primary" @click="nextQuestion">涓嬩竴棰�</el-button>
+                    <el-button
+                      class="tool-button"
+                      type="primary"
+                      @click="nextQuestion"
+                      >涓嬩竴棰�</el-button
+                    >
                   </div>
                 </div>
               </div>
@@ -78,7 +98,6 @@
         </div>
       </div>
     </div>
-
 
     <!-- 閫�鍑鸿�冭瘯鎻愮ず寮圭獥 -->
     <el-dialog v-model="quitDialog" title="娉ㄦ剰" width="500">
@@ -89,9 +108,7 @@
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="quitDialog = false">缁х画浣滅瓟</el-button>
-          <el-button type="danger" @click="confirmQuit">
-            纭畾閫�鍑�
-          </el-button>
+          <el-button type="danger" @click="confirmQuit"> 纭畾閫�鍑� </el-button>
         </div>
       </template>
     </el-dialog>
@@ -113,45 +130,50 @@
     </el-dialog>
 
     <!-- 鑰冭瘯鏃堕棿寮圭獥 -->
-    <el-dialog v-model="timeDialog" align-center width="500" :close-on-click-modal="false"
-      :close-on-press-escape="false" :show-close="false">
+    <el-dialog
+      v-model="timeDialog"
+      align-center
+      width="500"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :show-close="false"
+    >
       <div class="dialog-container flex flex-col items-center">
         <div class="icon-container">
           <el-icon :size="50" color="#3680fa">
             <Timer />
           </el-icon>
         </div>
-        <div class="dialog-info">
-          鑰冭瘯缁撴潫锛岀郴缁熻嚜鍔ㄦ敹鍗蜂腑.......
-        </div>
+        <div class="dialog-info">鑰冭瘯缁撴潫锛岀郴缁熻嚜鍔ㄦ敹鍗蜂腑.......</div>
       </div>
     </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, watchEffect, watch, onMounted } from 'vue';
-import { storeToRefs } from 'pinia';
-import { Close, Timer } from '@element-plus/icons-vue';
-import AnswerTag from './components/answer-tag/index.vue';
-import AnswerProgress from './components/answer-progress/index.vue';
-import AnswerSheet from './components/answer-sheet/index.vue';
-import AnswerTime from './components/answer-time/index.vue';
+import { ref, watchEffect, watch, onMounted ,onUnmounted} from "vue";
+import { storeToRefs } from "pinia";
+import { Close, Timer } from "@element-plus/icons-vue";
+import AnswerTag from "./components/answer-tag/index.vue";
+import AnswerProgress from "./components/answer-progress/index.vue";
+import AnswerSheet from "./components/answer-sheet/index.vue";
+import AnswerTime from "./components/answer-time/index.vue";
 
-import AnswerSingle from './components/answer-main/answer-single/index.vue';
-import AnswerMultiple from './components/answer-main/answer-multiple/index.vue';
-import AnswerAudio from './components/answer-main/answer-audio/index.vue';
-import AnswerFill from './components/answer-main/answer-fill/index.vue';
-import AnswerDetermine from './components/answer-main/answer-determine/index.vue';
-import AnswerShort from './components/answer-main/answer-short/index.vue';
-import AnswerCount from './components/answer-main/answer-count/index.vue';
+import AnswerSingle from "./components/answer-main/answer-single/index.vue";
+import AnswerMultiple from "./components/answer-main/answer-multiple/index.vue";
+import AnswerAudio from "./components/answer-main/answer-audio/index.vue";
+import AnswerFill from "./components/answer-main/answer-fill/index.vue";
+import AnswerDetermine from "./components/answer-main/answer-determine/index.vue";
+import AnswerShort from "./components/answer-main/answer-short/index.vue";
+import AnswerCount from "./components/answer-main/answer-count/index.vue";
 
-import { useExamStore, useUserStore } from '@/store/index.js';
-import { useRouter } from 'vue-router';
+import { useExamStore, useUserStore } from "@/store/index.js";
+import { useRouter } from "vue-router";
 
-import { submitExam } from '@/api/modules/exam.js';
-import useWebScoket from '@/hooks/useWebScoket.js';
+import { submitExam } from "@/api/modules/exam.js";
+import useWebScoket from "@/hooks/useWebScoket.js";
 
+import { ElMessage, ElMessageBox } from "element-plus";
 
 const router = useRouter();
 
@@ -159,7 +181,8 @@
 const userStore = useUserStore();
 
 const { userInfo } = storeToRefs(userStore);
-const { currentType, currentIndex, examDetail, examType, examInfo } = storeToRefs(examStore);
+const { currentType, currentIndex, examDetail, examType, examInfo } =
+  storeToRefs(examStore);
 
 const typeComponent = {
   1: AnswerSingle,
@@ -181,11 +204,11 @@
 //   heartBeatData: 'ping'
 // });
 
-const { status, message, error, connect, disconnect, sendMessage } = useWebScoket({
-  url: '//192.168.3.64:8000/websocket/' + userInfo.value.id,
-  heartBeatData: 'ping'
-});
-
+const { status, message, error, connect, disconnect, sendMessage } =
+  useWebScoket({
+    url: "//192.168.3.64:8000/websocket/" + userInfo.value.id,
+    heartBeatData: "ping",
+  });
 
 // 涓婁竴棰�
 const prevQuestion = () => {
@@ -220,19 +243,18 @@
         currentType.value = typeQuestion.questionType;
         currentIndex.value = typeQuestion.questionList.length - 1;
       }
-
     } else if (currentIndex.value < 0) {
       tempIndex--;
       if (examDetail.value[tempIndex]) {
         currentType.value = examDetail.value[tempIndex].questionType;
-        currentIndex.value = examDetail.value[tempIndex].questionList.length - 1;
+        currentIndex.value =
+          examDetail.value[tempIndex].questionList.length - 1;
       } else {
         currentType.value = typeQuestion.questionType;
         currentIndex.value = 0;
       }
     }
   }
-
 };
 
 // 閫�鍑鸿�冭瘯
@@ -262,18 +284,20 @@
 const timeOut = () => {
   const temp = {
     ...examInfo.value,
-    titleList: examDetail.value
+    titleList: examDetail.value,
   };
   timeDialog.value = true;
   resetAllDialog();
 
   disconnect();
 
-  submitExam(temp).then(res => {
-    returnBack();
-  }).catch(() => {
-    returnBack();
-  });
+  submitExam(temp)
+    .then((res) => {
+      returnBack();
+    })
+    .catch(() => {
+      returnBack();
+    });
 };
 
 const returnBack = () => {
@@ -284,30 +308,55 @@
 
 watchEffect(() => {
   let progress = 0;
-  examDetail.value.forEach(item => {
-    item.questionList.forEach(question => {
-      if (question.answer || (Array.isArray(question.answerList) && question.answerList.length)) {
+  examDetail.value.forEach((item) => {
+    item.questionList.forEach((question) => {
+      if (
+        question.answer ||
+        (Array.isArray(question.answerList) && question.answerList.length)
+      ) {
         progress += 1;
       }
     });
   });
   examStore.setProgress(progress);
 });
-const answerTime = ref()
-watch(() => message.value, (msg) => {
-  if(msg.commend=="delayed"){
-    answerTime.value.addTime(msg.data.addTimeM)
-  }else if(msg.commend=="forceSubmit"){
-    confirmSubmit()
+const answerTime = ref();
+watch(
+  () => message.value,
+  (msg) => {
+    if (msg.commend == "delayed") {
+      answerTime.value.addTime(msg.data.addTimeM);
+    } else if (msg.commend == "forceSubmit") {
+      confirmSubmit();
+    }
   }
-});
-
+);
+// document.addEventListener('blur', function() {
+//     console.log('椤甸潰澶卞幓鐒︾偣');
+// });
+const warningNum = ref(0)
+const handleBlur = () => {
+  if (document.visibilityState === "visible") {
+  } else if (document.visibilityState === "hidden") {
+    ElMessageBox.alert("鑰冭瘯杩囩▼涓鍕跨寮�鑰冭瘯椤甸潰锛佽鍛婁笁娆″皢寮哄埗鏀跺嵎", "璀﹀憡", {
+      confirmButtonText: "纭畾",
+    });
+    warningNum.value++
+    if (warningNum.value==3) {
+      timeOut();
+    }
+  }
+};
 
 // -----------------------------------鐢熷懡鍛ㄦ湡
 onMounted(() => {
   // 杩炴帴webscoket
   connect();
+  document.addEventListener("visibilitychange", handleBlur);
 });
+onUnmounted(()=>{
+  document.removeEventListener("visibilitychange", handleBlur);
+})
 </script>
 
 <style lang="scss" scoped>
diff --git a/src/views/home/components/user-panel/index.vue b/src/views/home/components/user-panel/index.vue
index f6b415c..ed05489 100644
--- a/src/views/home/components/user-panel/index.vue
+++ b/src/views/home/components/user-panel/index.vue
@@ -1,73 +1,62 @@
 <template>
-    <div class="user-panel max-w-sm min-w-96 h-fit">
-        <el-card class="card">
-            <div class="panel-content flex flex-col items-center">
-                <!-- 瀛︾敓淇℃伅 -->
-                <el-dropdown>
-                    <div
-                        class="avatar-container w-40 h-40 rounded-full overflow-hidden el-dropdown-link"
-                        @click="stateClick()"
-                    >
-                        <div
-                            class="avatar-content"
-                            v-if="userInfo.imagePath"
-                        >
-                            <img
-                                :src="userInfo.imagePath"
-                                class="avatar-img"
-                                alt=""
-                            />
-                        </div>
-                        <div
-                            class="avatar-content"
-                            :style="{ backgroundColor: getColor }"
-                            v-else
-                        >
-                            <div class="name text-5xl font-bold text-white">
-                                {{ getFirstName }}
-                            </div>
-                        </div>
-                    </div>
-                    <template #dropdown>
-                        <el-dropdown-menu>
-                            <el-dropdown-item>淇敼瀵嗙爜</el-dropdown-item>
-                            <el-dropdown-item>涓汉涓績</el-dropdown-item>
-                            <el-dropdown-item @click="quit()"
-                                >閫�鍑虹櫥褰�</el-dropdown-item
-                            >
-                        </el-dropdown-menu>
-                    </template>
-                </el-dropdown>
-
-                <div class="name-container text-lg font-bold mt-5 mb-2">
-                    {{ userInfo.realName }}
-                </div>
-
-                <div class="department-container text-base mb-10">
-                    {{ userInfo.userName }}
-                </div>
-                <!-- 瀛︾敓閫夐」 -->
-                <div class="tool-container grid grid-cols-3 gap-10">
-                    <div
-                        class="tool-item text-center cursor-pointer"
-                        v-for="item in toolList"
-                        @click="toolClick(item)"
-                    >
-                        <div class="tool-icon mb-1">
-                            <img
-                                :src="item.iconPath"
-                                class="width-img"
-                                alt=""
-                            />
-                        </div>
-                        <div class="tool-title">
-                            {{ item.title }}
-                        </div>
-                    </div>
-                </div>
+  <div class="user-panel max-w-sm min-w-96 h-fit">
+    <el-card class="card">
+      <div class="panel-content flex flex-col items-center">
+        <!-- 瀛︾敓淇℃伅 -->
+        <el-dropdown>
+          <div
+            class="avatar-container w-40 h-40 rounded-full overflow-hidden el-dropdown-link"
+            @click="stateClick()"
+          >
+            <div class="avatar-content" v-if="userInfo.imagePath">
+              <img :src="'/api/files/'+userInfo.imagePath" class="avatar-img" alt="" />
             </div>
-        </el-card>
-    </div>
+            <div
+              class="avatar-content"
+              :style="{ backgroundColor: getColor }"
+              v-else
+            >
+              <div class="name text-5xl font-bold text-white">
+                {{ getFirstName }}
+              </div>
+            </div>
+          </div>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item>淇敼瀵嗙爜</el-dropdown-item>
+              <el-dropdown-item @click="goPersonalCenter()"
+                >涓汉涓績</el-dropdown-item
+              >
+              <el-dropdown-item @click="quit()">閫�鍑虹櫥褰�</el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+
+        <div class="name-container text-lg font-bold mt-5 mb-2">
+          {{ userInfo.realName }}
+        </div>
+
+        <div class="department-container text-base mb-10">
+          {{ userInfo.userName }}
+        </div>
+        <!-- 瀛︾敓閫夐」 -->
+        <div class="tool-container grid grid-cols-3 gap-10">
+          <div
+            class="tool-item text-center cursor-pointer"
+            v-for="item in toolList"
+            @click="toolClick(item)"
+          >
+            <div class="tool-icon mb-1">
+              <img :src="item.iconPath" class="width-img" alt="" />
+            </div>
+            <div class="tool-title">
+              {{ item.title }}
+            </div>
+          </div>
+        </div>
+      </div>
+    </el-card>
+  </div>
 </template>
 
 <script setup>
@@ -84,86 +73,89 @@
 const router = useRouter();
 // 鑾峰彇鍒楄〃
 const toolList = ref([
-    {
-        id: 1,
-        title: "鍦ㄧ嚎鍩硅",
-        iconPath: new URL("@/assets/icons/icon1.png", import.meta.url).href,
-        path: "/train",
-    },
-    {
-        id: 2,
-        title: "鎴戠殑鑰冭瘯",
-        iconPath: new URL("@/assets/icons/icon2.png", import.meta.url).href,
-        path: "/exam-list",
-    },
-    {
-        id: 3,
-        title: "鎴戠殑鎴愮哗",
-        iconPath: new URL("@/assets/icons/icon2.png", import.meta.url).href,
-        path: "/grade-list",
-    },
+  {
+    id: 1,
+    title: "鍦ㄧ嚎鍩硅",
+    iconPath: new URL("@/assets/icons/icon1.png", import.meta.url).href,
+    path: "/train",
+  },
+  {
+    id: 2,
+    title: "鎴戠殑鑰冭瘯",
+    iconPath: new URL("@/assets/icons/icon2.png", import.meta.url).href,
+    path: "/exam-list",
+  },
+  {
+    id: 3,
+    title: "鎴戠殑鎴愮哗",
+    iconPath: new URL("@/assets/icons/icon2.png", import.meta.url).href,
+    path: "/grade-list",
+  },
 ]);
 
 // 鑾峰彇棰滆壊
 const getColor = computed(() => {
-    return randomColor();
+  return randomColor();
 });
 // 鑾峰彇鐢ㄦ埛淇℃伅
 const getFirstName = computed(() => {
-    return userInfo.value.realName && userInfo.value.realName[0];
+  return userInfo.value.realName && userInfo.value.realName[0];
 });
 // 鐐瑰嚮浜嬩欢
 const toolClick = (item) => {
-    if (item.path) {
-        router.push(item.path);
-    }
+  if (item.path) {
+    router.push(item.path);
+  }
 };
 
 // 鐢ㄦ埛閫夐」
 const dropdownRef = ref();
 // 閫�鍑虹櫥褰�
 const quit = () => {
-    logout()
-        .then(() => {
-            router.push("/login").then(() => {
-                userStore.setUserInfo(null);
-                localStorage.clear("user");
-            });
-        })
-        .catch((err) => {
-            console.log("閫�鍑虹櫥褰曞け璐�,澶辫触鍘熷洜;", err);
-        });
+  logout()
+    .then(() => {
+      router.push("/login").then(() => {
+        userStore.setUserInfo(null);
+        localStorage.clear("user");
+      });
+    })
+    .catch((err) => {
+      console.log("閫�鍑虹櫥褰曞け璐�,澶辫触鍘熷洜;", err);
+    });
+};
+const goPersonalCenter = () => {
+  router.push("/personal-center");
 };
 </script>
 
 <style lang="scss" scoped>
 .card {
-    border-radius: 30px;
+  border-radius: 30px;
 }
 
 .avatar-content {
-    width: 100%;
-    height: 100%;
-    display: flex;
-    justify-content: center;
-    align-items: center;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 
 .avatar-img {
-    width: 100%;
-    height: 100%;
-    object-fit: cover;
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
 }
 .avatar-container {
-    cursor: pointer;
-    color: var(--el-color-primary);
-    display: flex;
-    align-items: center;
+  cursor: pointer;
+  color: var(--el-color-primary);
+  display: flex;
+  align-items: center;
 }
 .example-showcase .el-dropdown-link {
-    cursor: pointer;
-    color: var(--el-color-primary);
-    display: flex;
-    align-items: center;
+  cursor: pointer;
+  color: var(--el-color-primary);
+  display: flex;
+  align-items: center;
 }
 </style>
diff --git a/src/views/personal-center/index.vue b/src/views/personal-center/index.vue
new file mode 100644
index 0000000..26980b4
--- /dev/null
+++ b/src/views/personal-center/index.vue
@@ -0,0 +1,195 @@
+<template>
+  <div
+    class="exam-list-container w-screen h-screen bg-slate-50 flex flex-col items-center"
+  >
+    <NormalHeader class="shrink-0"></NormalHeader>
+
+    <div class="list-container container grow relative">
+      <div
+        class="personal-center-box list-content absolute top-0 bottom-0 left-0 right-0 py-4"
+      >
+        <div class="information">
+          <el-card class="h-full" :body-style="{ height: '50%' }">
+            <div
+              class="card-wrapper w-full h-full flex flex-col px-8 box-border"
+            >
+              <div>涓汉淇℃伅</div>
+              <div class="img-box">
+                <input
+                  type="file"
+                  @change="changeHeadPortrait"
+                  accept=".jpg, .png"
+                  style="display: none"
+                  ref="fileHeadPortrait"
+                  id="fileHeadPortrait"
+                />
+                  <img
+                    class="img"
+                    id="headPortrait"
+                    @click="uploadImage"
+                    :src="'api/files/' + userData.imagePath"
+                  />
+                <span>{{ userData.userName }}</span>
+              </div>
+              <div>
+                <el-form label-width="auto" style="max-width: 600px">
+                  <el-form-item label="濮撳悕">
+                    {{ userData.realName }}
+                  </el-form-item>
+                  <el-form-item label="鐝骇">
+                    {{ userData.className.join(",") }}
+                  </el-form-item>
+                  <el-form-item label="娉ㄥ唽鏃堕棿">
+                    {{ timestampToDate(userData.createTime) }}
+                  </el-form-item>
+                </el-form>
+              </div>
+            </div>
+          </el-card>
+        </div>
+        <div class="list-wrapper w-full h-full">
+          <el-card class="h-full" :body-style="{ height: '100%' }">
+            <el-tabs v-model="activeName" class="demo-tabs">
+              <el-tab-pane label="璧勬枡淇敼" name="information">
+                <el-form
+                  :model="informationForm"
+                  label-width="auto"
+                  style="max-width: 600px"
+                >
+                  <el-form-item label="鐪熷疄濮撳悕">
+                    <el-input v-model="informationForm.name" />
+                  </el-form-item>
+                  <el-form-item label="骞撮緞">
+                    <el-input v-model="informationForm.age" />
+                  </el-form-item>
+                  <el-form-item label="鎬у埆">
+                    <el-select
+                      v-model="informationForm.sex"
+                      style="width: 100px"
+                    >
+                      <el-option label="鐢�" value="1" />
+                      <el-option label="濂�" value="2" />
+                    </el-select>
+                  </el-form-item>
+                  <el-form-item label="鍑虹敓骞存湀">
+                    <el-date-picker
+                      v-model="informationForm.birthDay"
+                      type="date"
+                      placeholder="Pick a day"
+                      :size="size"
+                    />
+                  </el-form-item>
+                  <el-form-item label="鎵嬫満">
+                    <el-input
+                      v-model="informationForm.phone"
+                    /> </el-form-item></el-form
+              ></el-tab-pane>
+              <el-tab-pane label="瀵嗙爜淇敼" name="password">
+                <el-form
+                  :model="passwordForm"
+                  label-width="auto"
+                  style="max-width: 600px"
+                >
+                  <el-form-item label="鏃у瘑鐮�">
+                    <el-input v-model="passwordForm.name" />
+                  </el-form-item>
+                  <el-form-item label="鏂板瘑鐮�">
+                    <el-input v-model="passwordForm.name" />
+                  </el-form-item>
+                  <el-form-item label="纭瀵嗙爜">
+                    <el-input v-model="passwordForm.name" />
+                  </el-form-item>
+                </el-form>
+              </el-tab-pane>
+            </el-tabs>
+          </el-card>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { uploadImg } from "@/api/modules/personalCenter.js";
+import { ElMessage } from "element-plus";
+
+const activeName = ref("information");
+const userData = ref(JSON.parse(localStorage.getItem("user")).userInfo);
+const informationForm = ref({
+  name: userData.value.realName,
+  sex: userData.value.sex,
+  age: userData.value.age,
+  phone: userData.value.phone,
+  birthDay: userData.value.birthDay,
+});
+const passwordForm = ref({ name: "" });
+//澶村儚涓婁紶
+let formData = new FormData();
+const uploadImage = () => {
+  let logoFile = document.getElementById("fileHeadPortrait");
+  if (logoFile) {
+    logoFile.click();
+  }
+};
+const changeHeadPortrait = (e) => {
+  // let platformLogo = document.getElementById("headPortrait");
+  if (e.target.files[0]) {
+    // let reader = new FileReader();
+    // reader.onload = function (e) {
+    //   if (platformLogo) {
+    //     platformLogo.setAttribute("src", e.target.result);
+    //   }
+    // };
+    // reader.readAsDataURL(e.target.files[0]);
+    formData.set("file", e.target.files[0]);
+    uploadImg(formData).then(
+      () => {
+        ElMessage({
+          showClose: true,
+          message: "涓婁紶鎴愬姛",
+          type: "success",
+        });
+        // window.location.reload();
+      },
+      (err) => {
+        ElMessage.error(err.data.errorMsg);
+      }
+    );
+  }
+};
+function timestampToDate(timestamp) {
+  const date = new Date(timestamp); // 灏嗘椂闂存埑杞崲涓篋ate瀵硅薄
+  const options = { year: "numeric", month: "long", day: "numeric" }; // 瀹氫箟鏃ユ湡鏍煎紡
+  return new Intl.DateTimeFormat("zh-CN", options).format(date); // 浣跨敤Intl.DateTimeFormat杩涜鏍煎紡鍖�
+}
+</script>
+
+<style lang="scss" scoped>
+:deep(.el-tabs__nav-wrap:after) {
+  display: none;
+}
+.personal-center-box {
+  display: flex;
+  justify-content: space-between;
+}
+.information {
+  width: 500px;
+  margin-right: 20px;
+}
+.img-box {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  margin-top: 30px;
+  margin-bottom: 30px;
+}
+.img {
+  height: 150px;
+  width: 150px;
+  border-radius: 100px;
+  overflow: hidden;
+  object-fit: cover;
+}
+</style>
diff --git a/vite.config.js b/vite.config.js
index 91c5690..6bccd72 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -30,7 +30,7 @@
         proxy: {
             '/api': {
                 // target: 'http://192.168.3.88:8000',
-                target: 'http://42.193.1.25:8000',
+                target: 'http://192.168.3.64:8000',
                 changeOrigin: true,
             }
         }

--
Gitblit v1.8.0