瀏覽代碼

完善用户资料修改

枫叶秋林 2 年之前
父節點
當前提交
1f25bafb53

+ 3 - 0
components.d.ts

@@ -18,6 +18,7 @@ declare module '@vue/runtime-core' {
     ElCarousel: typeof import('element-plus/es')['ElCarousel']
     ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElCod: typeof import('element-plus/es')['ElCod']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElContainer: typeof import('element-plus/es')['ElContainer']
     ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
@@ -54,6 +55,8 @@ declare module '@vue/runtime-core' {
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTag: typeof import('element-plus/es')['ElTag']
+    ElTooltip: typeof import('element-plus/es')['ElTooltip']
+    ElUpload: typeof import('element-plus/es')['ElUpload']
     Hleft: typeof import('./src/components/header/hleft.vue')['default']
     Hright: typeof import('./src/components/header/hright.vue')['default']
     Hsearch: typeof import('./src/components/header/hsearch.vue')['default']

二進制
dump.rdb


+ 1 - 1
src/components/auth/login.vue

@@ -2,7 +2,7 @@
 import { type } from 'os'
 import { reactive, ref } from 'vue'
 import service from '../../plugins/axios'
-import { token, userdata } from '../../plugins/pinia'
+import { token, Avatar } from '../../plugins/pinia'
 import { encrypt } from '../../plugins/crypto'
 import { ElMessage, FormInstance } from 'element-plus'
 const form = reactive({

+ 5 - 3
src/components/header/hleft.vue

@@ -3,11 +3,13 @@ import { useDark, useToggle } from '@vueuse/core'
 import { Sunny, Moon } from '@element-plus/icons-vue'
 import userinfo from './userinfo.vue'
 import router from '../../plugins/router'
-import { token } from '../../plugins/pinia'
+import { token, Avatar } from '../../plugins/pinia'
 const isDark = useDark()
 const toggleDark = useToggle(isDark)
 const to_login = async () => {
-  await router.push('/auth')
+  //跳转到主页
+  // await router.push('/auth?type=login')
+  window.location.href = '/auth?type=login'
 }
 </script>
 
@@ -28,7 +30,7 @@ const to_login = async () => {
         <div>
           <el-avatar id="login_avatar"
                      v-show="token().token"
-                     src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" />
+                     :src="Avatar().URL" />
         </div>
       </template>
       <Suspense>

+ 36 - 15
src/components/header/userinfo.vue

@@ -1,17 +1,33 @@
 <script setup lang="ts">
 import { token } from '../../plugins/pinia'
 import service from '../../plugins/axios'
-import { reactive, ref } from 'vue'
+import { Avatar, userdata } from '../../plugins/pinia'
+import { ref } from 'vue'
+import { API_URL } from '../../config'
+
 const info = ref()
-const data = await service({
-  url: '/userinfo',
-  method: 'get',
-  headers: {
-    Authorization: 'Bearer ' + token().token,
-  },
-})
-if (data.data.cod === 200) {
-  info.value = data.data.data
+if (token().token) {
+  const data = await service({
+    url: '/userinfo',
+    method: 'get',
+    headers: {
+      Authorization: 'Bearer ' + token().token,
+    },
+  })
+  if (data.data.cod === 200) {
+    info.value = data.data.data
+
+    Avatar().URL = info.value.user.avatar
+      ? API_URL + info.value.user.avatar
+      : 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'
+    userdata().username = info.value.username
+    userdata().email = info.value.email
+    userdata().level = info.value.user.level
+    userdata().exp = info.value.user.exp
+  }
+}
+const to_userinfo = () => {
+  window.location.href = '/user/userinfo'
 }
 //隐藏多余字符串
 function hideStr(str: string) {
@@ -19,6 +35,7 @@ function hideStr(str: string) {
 }
 const outlogin = () => {
   token().token = ''
+  window.location.href = '/'
 }
 </script>
 
@@ -26,15 +43,17 @@ const outlogin = () => {
   <el-row>
     <el-space wrap>
       <el-col :span="10">
-        <el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" />
+        <el-avatar :src="Avatar().URL" />
       </el-col>
       <el-col :span="16">
         <el-row>
           <el-col :span="24">
-            <el-button type="text">{{hideStr(info.username)}}</el-button>
+            <el-button type="text"
+                       @click="to_userinfo">{{hideStr(info.username)}}</el-button>
           </el-col>
           <el-col :span="24">
-            <el-button type="text">个人主页</el-button>
+            <el-button type="text"
+                       @click="to_userinfo">个人主页</el-button>
           </el-col>
         </el-row>
       </el-col>
@@ -43,10 +62,12 @@ const outlogin = () => {
 
       <el-space wrap>
         <div>
-          <el-button type="text">[用户]lv.{{info.user.level}}</el-button>
+          <el-button type="text"
+                     @click="to_userinfo">[用户]lv.{{info.user.level}}</el-button>
         </div>
         <div>
-          <el-button type="text">{{info.user.exp}}/max?</el-button>
+          <el-button type="text"
+                     @click="to_userinfo">{{info.user.exp}}/max?</el-button>
         </div>
       </el-space>
 

+ 14 - 9
src/components/plate/platePostItem.vue

@@ -1,6 +1,7 @@
 <script setup lang="ts">
 import service from '../../plugins/axios'
-import { defineProps } from 'vue'
+import { defineProps, ref } from 'vue'
+import { API_URL } from '../../config'
 
 const dp = defineProps({
   id: {
@@ -45,19 +46,23 @@ function formatDate(date: any) {
   }
   return Math.ceil(diff / (3600 * 24)) + '天前'
 }
-
-const userinfo = await (
-  await service.get(`userinfo/getuser?id=${dp.authorid}`)
-).data
-console.log(JSON.stringify(userinfo))
+const userinfo = ref(
+  await (
+    await service.get(`userinfo/getuser?id=${dp.authorid}`)
+  ).data
+)
+userinfo.value.data.user.avatar = API_URL + userinfo.value.data.user.avatar
 </script>
 
 <template>
   <el-card>
     <el-row>
       <el-col :span="4">
-        <el-avatar :src="userinfo.data.user.avatar? userinfo.data.user.avatar : 'https://avatars.githubusercontent.com/u/25154432?v=4'"
-                   size="large" />
+        <div>
+          <el-avatar :src="userinfo.data.user.avatar? userinfo.data.user.avatar : 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'"
+                     size="large" />
+        </div>
+
       </el-col>
       <el-col :span="20">
         <span class="title"
@@ -67,7 +72,7 @@ console.log(JSON.stringify(userinfo))
         <div type="text"
              style="float: left">
           <el-space wrap>
-            <el-tag type="primary">{{ userinfo.data.username }}</el-tag>
+            <el-tag type="primary">作者:{{ userinfo.data.user.nickname }}</el-tag>
             <el-tag type="info">{{ formatDate(dp.time) }}</el-tag>
             <el-tag type="success">回复{{ dp.numberOfReplies?numberOfReplies:0 }}</el-tag>
             <el-tag type="warning">浏览量~开发中</el-tag>

+ 8 - 0
src/config.ts

@@ -0,0 +1,8 @@
+// 生产环境api地址
+const PROD_API_URL = "http://api.prod.com";
+// 开发环境api地址
+const DEV_API_URL = "http://127.0.0.1:3000/";
+//判断当前环境
+const isProd = process.env.NODE_ENV === "production";
+//根据环境判断api地址
+export const API_URL = isProd ? PROD_API_URL : DEV_API_URL;

+ 12 - 0
src/layouts/auth.vue

@@ -3,6 +3,10 @@ import { onMounted } from 'vue'
 import cod from './../components/cod/index.vue'
 import register from './../components/auth/register.vue'
 import login from './../components/auth/login.vue'
+
+import { useRoute } from 'vue-router'
+const router = useRoute()
+
 onMounted(() => {
   const avatar = document.getElementById('avatar')
   const register = document.getElementsByClassName('box-card')[0] as HTMLElement
@@ -85,6 +89,14 @@ onMounted(() => {
   document.getElementById('to_register')?.addEventListener('click', () => {
     register.click()
   })
+  if (router.query.type) {
+    if (router.query.type === 'login') {
+      login.click()
+    }
+    if (router.query.type === 'register') {
+      register.click()
+    }
+  }
 })
 </script>
 

+ 132 - 0
src/layouts/userinfo.vue

@@ -0,0 +1,132 @@
+
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { Avatar, userdata, token } from '../plugins/pinia'
+const avatarimg = 'https://jsonplaceholder.typicode.com/posts/'
+const gotoPlate = (id: number) => {
+  switch (id) {
+    case 1:
+      console.log('修改资料')
+      window.location.href = `/user/userinfo`
+      break
+    case 2:
+      console.log('我的帖子')
+      window.location.href = `/user/postlist/0?type=user`
+      break
+    case 3:
+      console.log('账户安全')
+      break
+  }
+}
+const exp = ref(`经验值:${userdata().exp}/max`)
+const logout = () => {
+  const outlogin = () => {
+    token().token = ''
+    window.location.href = '/'
+  }
+}
+</script>
+
+<template>
+  <el-card class="usercard">
+    <el-row :gutter="10">
+      <el-col :span="5">
+        <div class="center">
+          <el-avatar :size="80"
+                     :src="Avatar().URL" />
+        </div>
+      </el-col>
+      <el-col :span="7">
+        <!-- 用户名 -->
+        <div class="username">
+          {{ userdata().username?userdata().username:"获取失败" }}
+          <el-tooltip class="item"
+                      effect="dark"
+                      :content="exp"
+                      placement="right">
+            <el-tag type="warning">lv.{{ userdata().level }}</el-tag>
+          </el-tooltip>
+        </div>
+        <div class="userel">
+          {{ userdata().email?userdata().email:"获取失败" }}
+        </div>
+      </el-col>
+
+    </el-row>
+    <el-button type="danger"
+               size="mini"
+               class="logout"
+               @click="logout">退出登陆</el-button>
+  </el-card>
+  <br />
+  <el-row :gutter="10">
+    <el-col :xs="24"
+            :sm="24"
+            :md="8"
+            :lg="8"
+            :xl="8">
+      <el-card style="margin: 10px 0px;">
+        <el-menu class="hidden-xs-only hidden-sm-only"
+                 default-active="1"
+                 router="#default">
+          <el-menu-item index="/user/userinfo">修改资料</el-menu-item>
+          <el-menu-item index="/user/postlist/0?type=user">我的帖子</el-menu-item>
+          <el-menu-item index="3">账户安全</el-menu-item>
+        </el-menu>
+        <el-menu mode="horizontal"
+                 class="hidden-md-and-up"
+                 default-active="1"
+                 router="#default">
+          <el-menu-item index="/user/userinfo">修改资料</el-menu-item>
+          <el-menu-item index="/user/postlist/0?type=user">我的帖子</el-menu-item>
+          <el-menu-item index="3">账户安全</el-menu-item>
+        </el-menu>
+      </el-card>
+
+    </el-col>
+    <el-col :xs="24"
+            :sm="24"
+            :md="16"
+            :lg="16"
+            :xl="16">
+      <!--  -->
+      <div style="min-height: 300px;">
+        <Suspense>
+          <template #default>
+            <router-view></router-view>
+          </template>
+          <template #fallback>
+            <el-loading></el-loading>
+          </template>
+        </Suspense>
+      </div>
+    </el-col>
+  </el-row>
+
+</template>
+
+<style scoped >
+/* 居中 */
+.center {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.usercard {
+  position: relative;
+  /* 背景 */
+}
+.username {
+  margin: 10px 0;
+  font-size: 20px;
+}
+.userel {
+  font-size: 15px;
+}
+.logout {
+  position: absolute;
+  right: 10px;
+  top: 50px;
+}
+</style>

+ 10 - 6
src/plugins/axios.ts

@@ -16,18 +16,15 @@ service.interceptors.response.use(
     return response;
   },
   (error) => {
-    const { status, data } = error.response;
+    const { status, data, statusCode } = error.response;
     switch (status) {
       case 400:
         ElMessage.error(data.message);
         break;
       case 401:
-        //主页跳转不需要判断
-        if (router.currentRoute.value.path === "/home") {
-          return Promise.reject(error);
-        }
         ElMessage.error("token失效,请重新登录");
-        router.push("/auth");
+        window.localStorage.removeItem("token");
+        router.push("/auth?type=login");
         break;
       case 403:
         ElMessage.error("没有权限,请联系管理员");
@@ -38,6 +35,13 @@ service.interceptors.response.use(
       default:
         ElMessage.error(data.message);
     }
+    if (statusCode === 400) {
+      data.message.forEach((item: any) => {
+        item.msg.forEach((msg: any) => {
+          ElMessage.error(msg);
+        });
+      });
+    }
     return Promise.reject(error);
   }
 );

+ 20 - 0
src/plugins/pinia.ts

@@ -23,6 +23,26 @@ export const userdata = defineStore({
   state: () => {
     return {
       data: "",
+      username: "",
+      email: "",
+      level: 0,
+      exp: 0,
+    };
+  },
+  persist: {
+    enabled: true,
+    strategies: [
+      {
+        storage: localStorage,
+      },
+    ],
+  },
+});
+export const Avatar = defineStore({
+  id: "userdata",
+  state: () => {
+    return {
+      URL: "",
     };
   },
   persist: {

+ 14 - 0
src/plugins/router.ts

@@ -22,6 +22,20 @@ const router = createRouter({
           path: "/platelist/:id",
           component: () => import("../views/plate/platelist.vue"),
         },
+        {
+          path: "/user",
+          component: () => import("../layouts/userinfo.vue"),
+          children: [
+            {
+              path: "/user/userinfo",
+              component: () => import("../views/userinfo/putuserinfo.vue"),
+            },
+            {
+              path: "/user/postlist/:id",
+              component: () => import("../views/plate/platelist.vue"),
+            },
+          ],
+        },
       ],
     },
   ],

+ 37 - 1
src/views/plate/platelist.vue

@@ -2,21 +2,57 @@
 import { ref } from 'vue'
 import { useRoute } from 'vue-router'
 import service from '../../plugins/axios'
+import { token } from '../../plugins/pinia'
 import platePostItem from '../../components/plate/platePostItem.vue'
 let id = useRoute().params.id as any
 id = id ? id : 0
+let type: 'search' | 'user' | '' = ''
 const data = ref(
   await (
     await service.get(`/post/platelist?plateid=${id}&page=${1}&limit=${5}`)
   ).data
 )
 if (useRoute().query.title) {
+  type = 'search'
   data.value = await (
     await service.get(`/post/search?title=${useRoute().query.title}&page=${1}`)
   ).data
 }
-
+if (useRoute().query.type) {
+  type = 'user'
+  if (useRoute().query.type === 'user') {
+    data.value = await (
+      await service({
+        url: `/post/getpostbyuserid?page=${1}`,
+        method: 'get',
+        headers: {
+          Authorization: `Bearer ${token().token}`,
+        },
+      })
+    ).data
+  }
+}
 const page = async (v: number) => {
+  if (type === 'search') {
+    data.value = await (
+      await service.get(
+        `/post/search?title=${useRoute().query.title}&page=${v}`
+      )
+    ).data
+    return
+  }
+  if (type === 'user') {
+    data.value = await (
+      await service({
+        url: `/post/getpostbyuserid?page=${v}&limit=${5}`,
+        method: 'get',
+        headers: {
+          Authorization: `Bearer ${token().token}`,
+        },
+      })
+    ).data
+    return
+  }
   data.value = await (
     await service.get(`/post/platelist?plateid=${id}&page=${v}&limit=${5}`)
   ).data

+ 179 - 0
src/views/userinfo/putuserinfo.vue

@@ -0,0 +1,179 @@
+
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import service from '../../plugins/axios'
+import { token } from '../../plugins/pinia'
+import { ElMessage } from 'element-plus'
+import { Camera } from '@element-plus/icons-vue'
+import { Avatar } from '../../plugins/pinia'
+import { API_URL } from '../../config'
+const username = ref('username')
+const avatarUrl = ref(Avatar().URL)
+//上传成功后的回调
+const handleAvatarSuccess = (res: any, file: any) => {
+  avatarUrl.value = URL.createObjectURL(file.raw)
+  Avatar().URL = avatarUrl.value
+}
+//上传前的回调
+const beforeAvatarUpload = (file: any) => {
+  const isJPG = file.type === 'image/jpeg'
+  const isLt2M = file.size / 1024 / 1024 < 2
+  if (!isJPG) {
+    ElMessage.error('上传头像图片只能是 JPG 格式!')
+  }
+  if (!isLt2M) {
+    ElMessage.error('上传头像图片大小不能超过 2MB!')
+  }
+  return isJPG && isLt2M
+}
+const data = (
+  await service.get('userinfo', {
+    headers: {
+      Authorization: 'Bearer ' + token().token,
+    },
+  })
+).data.data
+const form = ref(data)
+const sub = async (req: any) => {
+  const { QQ, github, signature, nickname } = form.value.user
+  console.log(QQ, github, signature, nickname)
+
+  const res = (
+    await service({
+      method: 'put',
+      url: `userinfo/updateuserinfo`,
+      headers: {
+        Authorization: 'Bearer ' + token().token,
+      },
+      data: {
+        QQ,
+        github,
+        signature,
+        nickname,
+      },
+    })
+  ).data
+
+  if (res.code == 200) {
+    ElMessage.success(res.msg)
+  } else {
+    ElMessage.success(res.msg)
+  }
+}
+</script>
+
+<template>
+  <el-card class="putuserinfo_card">
+    <template #header>
+      修改资料
+    </template>
+    <div class="center">
+      <!-- 上传头像 -->
+      <el-tooltip content="上传jpeg格式的图片,大小不超过2M"
+                  placement="right">
+        <el-upload :action="API_URL+'upload/Avatar'"
+                   :show-file-list="false"
+                   :on-success="handleAvatarSuccess"
+                   :before-upload="beforeAvatarUpload"
+                   :headers="{Authorization: 'Bearer ' + token().token}"
+                   class="avatar-uploader">
+          <el-avatar :size="80"
+                     :src="avatarUrl"></el-avatar>
+          <el-icon class="avatar-uploader-icon">
+            <Camera />
+          </el-icon>
+        </el-upload>
+      </el-tooltip>
+
+    </div>
+    <el-form label-position="top"
+             :model="form">
+      <el-row :gutter="20">
+        <el-col :xs="24"
+                :sm="24"
+                :md="12"
+                :lg="12"
+                :xl="12">
+          <el-form-item label="用户名">
+            <el-input disabled
+                      v-model="form.username"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :xs="24"
+                :sm="24"
+                :md="12"
+                :lg="12"
+                :xl="12">
+          <el-form-item label="昵称">
+            <el-input v-model="form.user.nickname"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :xs="24"
+                :sm="24"
+                :md="12"
+                :lg="12"
+                :xl="12">
+          <el-form-item label="QQ">
+            <el-input v-model="form.user.QQ"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :xs="24"
+                :sm="24"
+                :md="12"
+                :lg="12"
+                :xl="12">
+          <el-form-item label="github">
+            <el-input v-model="form.user.github"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :xs="24"
+                :sm="24"
+                :md="24"
+                :lg="24"
+                :xl="24">
+          <el-form-item label="个性签名">
+            <el-input maxlength="30"
+                      show-word-limit
+                      type="textarea"
+                      resize="none"
+                      v-model="form.user.signature"></el-input>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <!-- 分割线 -->
+    <el-divider></el-divider>
+    <!-- 右对齐修改按钮 -->
+
+    <el-button type="primary"
+               class="right"
+               @click="sub(form)">修改</el-button>
+
+  </el-card>
+</template>
+
+<style scoped>
+.putuserinfo_card {
+  width: 100%;
+  margin-top: 10px;
+}
+.right {
+  /* 去掉清浮动 */
+  margin-bottom: 10px;
+  float: right;
+}
+.avatar-uploader-icon {
+  position: absolute;
+  top: 40;
+  left: 40;
+  font-size: 28px;
+  color: red;
+  /* 加粗 */
+  font-weight: bold;
+}
+.avatar-uploader {
+  position: relative;
+  width: 80px;
+}
+</style>