Browse Source

feat:个人中心页面

白白 1 year ago
parent
commit
dd6befd76a
7 changed files with 233 additions and 108 deletions
  1. 1 1
      package.json
  2. 9 1
      src/apis/user.ts
  3. 9 0
      src/hooks/useFormatDate.ts
  4. 13 1
      src/router/index.ts
  5. 10 0
      src/types/dto.ts
  6. 14 0
      src/utils/convertUtil.ts
  7. 177 105
      src/views/Profile.vue

+ 1 - 1
package.json

@@ -16,7 +16,7 @@
     "@vue-office/docx": "^1.6.2",
     "axios": "^1.7.2",
     "docx-preview": "^0.3.2",
-    "element-plus": "^2.7.5",
+    "element-plus": "^2.8.1",
     "mitt": "^3.0.1",
     "pinia": "^2.1.7",
     "pinia-plugin-persistedstate": "^3.2.1",

+ 9 - 1
src/apis/user.ts

@@ -1,6 +1,6 @@
 import axiosInstance from "@/apis/axios.config";
 import { type ILoginUserFrom } from "@/types/user";
-import type {UserRegisterDTO} from "@/types/dto";
+import type {UserRegisterDTO, UserUpdateDTO} from "@/types/dto";
 
 const USER_PREFIX = "/api/user"
 
@@ -29,6 +29,14 @@ const userApis = {
 
     register: (userRegisterDTO: UserRegisterDTO) => {
         return axiosInstance.post(`${USER_PREFIX}/register`, userRegisterDTO)
+    },
+
+    updateUser: (id:number, userUpdateDTO:UserUpdateDTO) => {
+        return axiosInstance.put(`${USER_PREFIX}`, userUpdateDTO, {
+            params: {
+                id: id,
+            }
+        })
     }
 
 }

+ 9 - 0
src/hooks/useFormatDate.ts

@@ -11,7 +11,16 @@ export function useFormatDate(){
         return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
     };
 
+    const formatBirthday = (date:string) => {
+        const d = new Date(date);
+        const year = d.getFullYear();
+        const month = String(d.getMonth() + 1).padStart(2, '0');
+        const day = String(d.getDate()).padStart(2, '0');
+        return `${year}-${month}-${day}`;
+    };
+
     return {
         formatDate,
+        formatBirthday
     };
 }

+ 13 - 1
src/router/index.ts

@@ -104,6 +104,10 @@ const router = createRouter({
     },
 
     // 管理员相关界面
+    {
+      path: "/admin",
+      redirect: '/admin/login',
+    },
     {
       path: "/admin/login",
       name: 'adminLogin',
@@ -149,7 +153,15 @@ const router = createRouter({
  */
 router.beforeEach((to) => {
   const store = useStore()
-  const role = store.user.role
+  // 判断是否是登录页面,如果是则直接放行
+  if (to.path === '/login' || to.path === '/register' || to.path === '/admin/login') {
+    return true;
+  }
+  // 判断是否登录,未登录需要登录
+  if(!("role" in store.user)){
+    router.push('/login')
+  }
+  let role = store.user.role
   // 1.守卫管理员权限界面
   if(to.meta.roles != null){
     for(let i = 0; i < to.meta.roles.length; i++){

+ 10 - 0
src/types/dto.ts

@@ -54,3 +54,13 @@ export interface UserRegisterDTO {
     role: Role; // 用户角色:学生、教师、管理员不能缺失
 }
 
+export interface UserUpdateDTO {
+    userAvatar?: string;
+    lastLoginTime?: string;
+    englishName?: string;
+    gender?: string;
+    birthday?: string;
+    phone?: string;
+    contentEmail?: string;
+}
+

+ 14 - 0
src/utils/convertUtil.ts

@@ -0,0 +1,14 @@
+import type {UserUpdateDTO} from "@/types/dto";
+import type {IUser} from "@/types/user";
+
+export function convertIUserToUserUpdateDTO(user: IUser): UserUpdateDTO {
+    const userUpdateDTO: UserUpdateDTO = {};
+    // 遍历IUser对象的属性
+    for (const key in user) {
+        // 只选取UserUpdateDTO中定义的属性进行赋值
+        if (key === "userAvatar" || key === "lastLoginTime" || key === "englishName" || key === "gender" || key === "birthday" || key === "phone" || key === "contentEmail") {
+            userUpdateDTO[key] = user[key];
+        }
+    }
+    return userUpdateDTO;
+}

+ 177 - 105
src/views/Profile.vue

@@ -1,88 +1,128 @@
 <template>
   <div class="personal-center">
     <el-card class="box-card">
-      <div slot="header" class="clearfix">
-        <span style="font-size: 25px; font-weight: bold">个人中心</span>
-      </div>
-      <el-row :gutter="20">
-        <el-col :span="12" class="main-info">
-          <el-avatar :size="100" :src="user.userAvatar" />
-          <div>{{ user.name }}</div>
-          <div>{{ user.officialNumber }}</div>
-          <div>{{ user.officialEmail }}</div>
-        </el-col>
-        <el-col :span="12">
-          <el-descriptions title="账户信息" :column="1">
-            <template #extra>
-              <el-button type="primary" @click="visible=true">修改信息</el-button>
-            </template>
-            <el-descriptions-item label="用户名">{{ user.englishName }}</el-descriptions-item>
-            <el-descriptions-item label="性别">{{ user.gender }}</el-descriptions-item>
-            <el-descriptions-item label="注册时间">{{ user.createTime }}</el-descriptions-item>
-            <el-descriptions-item label="手机号">{{ user.phone }}</el-descriptions-item>
-            <el-descriptions-item label="生日">{{ user.birthday }}</el-descriptions-item>
-          </el-descriptions>
-        </el-col>
-      </el-row>
+<!--      <div slot="header" class="clearfix">-->
+<!--        <span style="font-size: 25px; font-weight: bold">个人中心</span>-->
+<!--      </div>-->
+<!--      <el-row :gutter="20">-->
+<!--        <el-col :span="12" class="main-info">-->
+<!--          <el-avatar :size="100" :src="user.userAvatar" />-->
+<!--          <div>{{ user.name }}</div>-->
+<!--          <div>{{ user.officialNumber }}</div>-->
+<!--          <div>{{ user.officialEmail }}</div>-->
+<!--        </el-col>-->
+<!--        <el-col :span="12">-->
+<!--          <el-descriptions title="账户信息" :column="1">-->
+<!--            <template #extra>-->
+<!--              <el-button type="primary" @click="visible=true">修改信息</el-button>-->
+<!--            </template>-->
+<!--            <el-descriptions-item label="英文名">{{ user.englishName }}</el-descriptions-item>-->
+<!--            <el-descriptions-item label="性别">{{ user.gender == 'MAN' ? '男': '女' }}</el-descriptions-item>-->
+<!--            <el-descriptions-item label="注册时间">{{ user.createTime }}</el-descriptions-item>-->
+<!--            <el-descriptions-item label="手机号">{{ user.phone }}</el-descriptions-item>-->
+<!--            <el-descriptions-item label="生日">{{ user.birthday }}</el-descriptions-item>-->
+<!--          </el-descriptions>-->
+<!--        </el-col>-->
+<!--      </el-row>-->
+      <el-descriptions
+          title="个人中心"
+          border
+          :column="3">
+          <template #extra>
+            <el-button type="primary" @click="visible=true">修改信息</el-button>
+          </template>
+        <el-descriptions-item
+            :rowspan="2"
+            :width="140"
+            label="Photo"
+            align="center"
+        >
+          <el-image
+              style="width: 100px; height: 100px"
+              :src="user.userAvatar"
+          />
+        </el-descriptions-item>
+        <el-descriptions-item label="Name">{{ user.name }}</el-descriptions-item>
+        <el-descriptions-item label="EnglishName">{{ user.englishName }}</el-descriptions-item>
+        <el-descriptions-item label="Gender">{{ user.gender == 'MAN' ? 'man': 'woman' }}</el-descriptions-item>
+        <el-descriptions-item label="Role">
+          <template v-if="user.role == 'STUDENT'">
+            <el-tag size="small" >Student</el-tag>
+          </template>
+          <template v-else>
+            <el-tag size="small" type="error">Teacher</el-tag>
+          </template>
+        </el-descriptions-item>
+        <el-descriptions-item label="Birthday">{{ user.birthday }}</el-descriptions-item>
+        <el-descriptions-item label="Phone">{{ user.phone }}</el-descriptions-item>
+        <el-descriptions-item label="OfficialNumber">{{ user.officialNumber }}</el-descriptions-item>
+        <el-descriptions-item label="contentEmail">{{ user.contentEmail }}</el-descriptions-item>
+        <el-descriptions-item label="OfficialEmail">{{ user.officialEmail }}</el-descriptions-item>
+      </el-descriptions>
     </el-card>
   </div>
 
   <el-dialog v-model="visible" title="编辑">
-    <el-form-item label="头像">
-      <el-upload
-          ref="uploadRef"
-          drag
-          action="/api/file"
-          multiple
-          v-model:file-list="fileList"
-          :limit="1"
-          :on-success="handleSuccess"
-          :on-error="handleError"
-          :on-exceed="handleExceed"
-          :on-change="handleChange"
-          list-type="picture-card"
-          accept=".png, .jpg"
-          :auto-upload="false"
-      >
-<!--        <el-icon class="el-icon&#45;&#45;upload"><upload-filled /></el-icon>-->
-        <div class="el-upload__text">
-          拖拽上传或<em>点击此处上传</em>
-        </div>
-<!--        <el-icon><Plus /></el-icon>-->
-        <template #tip>
-          <div class="el-upload__tip">
-            jpg/png 文件不大于 500kb
+    <el-form :model="userUpdateDTO" label-width="auto" ref="formRef">
+      <el-form-item label="头像">
+        <el-upload
+            ref="uploadRef"
+            drag
+            action="/api/file"
+            multiple
+            v-model:file-list="fileList"
+            :limit="1"
+            :on-success="handleSuccess"
+            :on-error="handleError"
+            :on-exceed="handleExceed"
+            :on-change="handleChange"
+            list-type="picture-card"
+            accept=".png, .jpg"
+            :auto-upload="false"
+        >
+          <div class="el-upload__text">
+            拖拽上传或<em>点击此处上传</em>
           </div>
-        </template>
-      </el-upload>
-    </el-form-item>
-    <el-form :model="user" label-width="auto">
-      <el-form-item label="姓名" prop="courseName">
-        <el-input v-model="user.name" placeholder="请输入名称"></el-input>
+          <template #tip>
+            <div class="el-upload__tip">
+              jpg/png 文件不大于 500kb
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="姓名" >
+        <el-input v-model="user.name" placeholder="请输入名称" disabled></el-input>
       </el-form-item>
-      <el-form-item label="学号" prop="courseName">
+      <el-form-item label="学号" >
         <el-input v-model="user.officialNumber" placeholder="请输入名称" disabled></el-input>
       </el-form-item>
-      <el-form-item label="邮箱" prop="courseName">
+      <el-form-item label="邮箱" >
         <el-input v-model="user.officialEmail" placeholder="请输入名称" disabled></el-input>
       </el-form-item>
-      <el-form-item label="用户名" prop="courseName">
-        <el-input v-model="user.englishName" placeholder="请输入名称"></el-input>
+      <el-form-item label="联系邮箱" >
+        <el-input v-model="userUpdateDTO.contentEmail" placeholder="请输入名称"></el-input>
+      </el-form-item>
+      <el-form-item label="英文名" >
+        <el-input v-model="userUpdateDTO.englishName" placeholder="请输入名称"></el-input>
       </el-form-item>
-      <el-form-item label="性别" prop="courseName">
-        <el-input v-model="user.gender" placeholder="请输入名称"></el-input>
+      <el-form-item label="性别" >
+        <el-radio-group v-model="userUpdateDTO.gender" placeholder="请输入名称">
+          <el-radio value="MAN">男</el-radio>
+          <el-radio value="WOMAN">女</el-radio>
+        </el-radio-group>
       </el-form-item>
-      <el-form-item label="手机号" prop="courseName">
-        <el-input v-model="user.phone" placeholder="请输入名称"></el-input>
+      <el-form-item label="手机号" >
+        <el-input v-model="userUpdateDTO.phone" placeholder="请输入名称"></el-input>
       </el-form-item>
-      <el-form-item label="生日" prop="courseName">
-        <el-input v-model="user.birthday" placeholder="请输入名称"></el-input>
+      <el-form-item label="生日" >
+        <el-date-picker v-model="userUpdateDTO.birthday" type="date" placeholder="生日" style="width: 180px"/>
       </el-form-item>
     </el-form>
     <template #footer>
       <div class="dialog-footer">
         <el-button @click="visible = false">取消</el-button>
-        <el-button type="primary" @click="submit">确定</el-button>
+<!--        <el-button type="primary" @click="submitForm(formRef)">确定</el-button>-->
+        <el-button type="primary" @click="handleSubmit">确定</el-button>
       </div>
     </template>
   </el-dialog>
@@ -100,17 +140,20 @@
     ElDescriptionsItem,
     ElTable,
     ElTableColumn,
-    ElMessage, type UploadUserFile, type UploadProps, type UploadFile, type UploadFiles
+    ElMessage, type UploadUserFile, type UploadProps, type UploadFile, type UploadFiles, type FormInstance
   } from 'element-plus';
     import useApis from "@/apis";
     import {useStore} from "@/store";
   import type {IUser} from "@/types/user";
-  import {Plus} from "@element-plus/icons-vue";
+  import {convertIUserToUserUpdateDTO} from "@/utils/convertUtil";
+  import type {UserUpdateDTO} from "@/types/dto";
+  import {useFormatDate} from "@/hooks/useFormatDate";
 
   const apis = useApis()
   const store = useStore()
 
   const user = reactive<IUser>({} as IUser)
+  let userUpdateDTO = reactive<UserUpdateDTO>({} as UserUpdateDTO)
   let visible = ref(false)
   const uploadRef = ref()
   // 用于编辑头像展示预览图片
@@ -119,7 +162,10 @@
   const fetchUserData = () => {
     apis.findUserById(store.user.id)
         .then((res:any) => {
-          Object.assign(user, res.data.data.records[0])
+          let data = res.data.data.records[0]
+          data = processData(data)
+          Object.assign(user, data)
+          Object.assign(userUpdateDTO, convertIUserToUserUpdateDTO(user))
         })
         .catch((err:any) => {
           ElMessage({
@@ -129,51 +175,77 @@
         })
   }
 
-  const submit = () => {
-    alert("TODO")
+  // 预处理个人信息的注册时间展示
+  const processData = (data:IUser) => {
+    data.createTime = useFormatDate().formatDate(data.createTime)
+    data.birthday = useFormatDate().formatBirthday(data.birthday)
+    return data
   }
 
-const handleSuccess: UploadProps['onSuccess'] = (response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
-  console.log("上传成功", response.data)
-  user.userAvatar = response.data
-}
+  const handleSubmit = () => {
+    console.log(userUpdateDTO)
+    apis.updateUser(user.id, userUpdateDTO)
+        .then((res:any) => {
+          ElMessage({
+            message: "个人信息修改成功",
+            type: "success"
+          })
+          fetchUserData()
+        })
+        .catch((err:any) => {
+          ElMessage({
+            message: "个人信息修改失败",
+            type: "error"
+          })
+          console.log(err)
+        })
+        .finally(() => {
+          visible.value = false
+        })
+  }
 
-const handleError: UploadProps['onError'] = (error: Error, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
-  console.log("上传失败", error)
-  console.log(error, uploadFile, uploadFiles)
-}
+  const handleSuccess: UploadProps['onSuccess'] = (response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+    console.log("上传成功", response.data)
+    userUpdateDTO.userAvatar = response.data
+  }
 
-const handleExceed = (files, fileList) => {
-  // console.log(files)
-  uploadRef.value.clearFiles()  //清空上传文件(限制一个,所以直接清空即可)
-  const file = files[0]
-  uploadRef.value.handleStart(file)  //重新上传
-}
+  const handleError: UploadProps['onError'] = (error: Error, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+    console.log("上传失败", error)
+    console.log(error, uploadFile, uploadFiles)
+  }
 
-//选择文件
-const handleChange = (file,fileList) => {
-  // console.log(file)
-  const fileImgType = file.name.match(/\.([^\.]+)$/)[1];  //匹配文件格式(最后一个'.'后的格式)或者匹配file.raw.type
-  const isImageType = ['png','jpg'];
-  const isLt5KB = file.size / 1024  < 500;  //判断图片格式与大小
-  if (isImageType.indexOf(fileImgType)==-1) {
-    ElMessage.error('文件仅支持png、jpg格式')  //限制文件类型
-    uploadRef.value.clearFiles()  //清空上传文件
-    return false;
+  const handleExceed = (files, fileList) => {
+    uploadRef.value.clearFiles()  //清空上传文件(限制一个,所以直接清空即可)
+    const file = files[0]
+    uploadRef.value.handleStart(file)  //重新上传
   }
-  if (!isLt5KB) {
-    ElMessage.error('文件不能超过500kb!')  //限制文件大小
-    uploadRef.value.clearFiles()  //清空上传文件
-    return false;
+
+  //选择文件
+  const handleChange = (file,fileList) => {
+    const fileImgType = file.name.match(/\.([^\.]+)$/)[1];  //匹配文件格式(最后一个'.'后的格式)或者匹配file.raw.type
+    const isImageType = ['png','jpg'];
+    const isLt5KB = file.size / 1024  < 500;  //判断图片格式与大小
+    if (isImageType.indexOf(fileImgType)==-1) {
+      ElMessage.error('文件仅支持png、jpg格式')  //限制文件类型
+      uploadRef.value.clearFiles()  //清空上传文件
+      return false;
+    }
+    if (!isLt5KB) {
+      ElMessage.error('文件不能超过500kb!')  //限制文件大小
+      uploadRef.value.clearFiles()  //清空上传文件
+      return false;
+    }
+    ElMessage.warning('请等待头像上传成功后,再保存信息')
+    apis.uploadFile(file.raw)
+        .then((res) => {
+          userUpdateDTO.userAvatar = res.data.data.split('?')[0]
+          ElMessage.success('头像上传成功')
+        })
+        .catch((err) => {
+          console.log(err)
+          ElMessage.error('头像上传失败')
+        })
   }
-  apis.uploadFile(file.raw)
-      .then((res) => {
-        user.userAvatar = res.data.data
-      })
-      .catch((err) => {
-        console.log(err)
-      })
-}
 
   onMounted(() => {
     fetchUserData()