Ver código fonte

feat: 新增个人中心页

kailong321200875 1 ano atrás
pai
commit
4146716655

+ 0 - 5
src/components/UserInfo/src/UserInfo.vue

@@ -62,11 +62,6 @@ const toPage = (path: string) => {
             {{ t('router.personalCenter') }}
           </div>
         </ElDropdownItem>
-        <ElDropdownItem>
-          <div @click="toPage('/personal/personal-setting')">
-            {{ t('router.personalSetting') }}
-          </div>
-        </ElDropdownItem>
         <ElDropdownItem>
           <div @click="toDocument">{{ t('common.document') }}</div>
         </ElDropdownItem>

+ 37 - 1
src/hooks/web/useValidator.ts

@@ -51,10 +51,46 @@ export const useValidator = () => {
     }
   }
 
+  const phone = (message?: string): FormItemRule => {
+    return {
+      validator: (_, val, callback) => {
+        if (!val) return callback()
+        if (!/^1[3456789]\d{9}$/.test(val)) {
+          callback(new Error(message || '请输入正确的手机号码'))
+        } else {
+          callback()
+        }
+      }
+    }
+  }
+
+  const email = (message?: string): FormItemRule => {
+    return {
+      validator: (_, val, callback) => {
+        if (!val) return callback()
+        if (!/^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/.test(val)) {
+          callback(new Error(message || '请输入正确的邮箱'))
+        } else {
+          callback()
+        }
+      }
+    }
+  }
+
+  const maxlength = (max: number): FormItemRule => {
+    return {
+      max,
+      message: '长度不能超过' + max + '个字符'
+    }
+  }
+
   return {
     required,
     lengthRange,
     notSpace,
-    notSpecialCharacters
+    notSpecialCharacters,
+    phone,
+    email,
+    maxlength
   }
 }

+ 0 - 1
src/locales/en.ts

@@ -186,7 +186,6 @@ export default {
     tableVideoPreview: 'Table video preview',
     cardTable: 'Card table',
     personalCenter: 'Personal center',
-    personalSetting: 'Personal setting',
     personal: 'Personal'
   },
   permission: {

+ 0 - 1
src/locales/zh-CN.ts

@@ -182,7 +182,6 @@ export default {
     tableVideoPreview: '表格视频预览',
     cardTable: '卡片表格',
     personalCenter: '个人中心',
-    personalSetting: '个人设置',
     personal: '个人'
   },
   permission: {

+ 0 - 10
src/router/index.ts

@@ -64,16 +64,6 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
           hidden: true,
           canTo: true
         }
-      },
-      {
-        path: 'personal-setting',
-        component: () => import('@/views/Personal/PersonalSetting/PersonalSetting.vue'),
-        name: 'PersonalSetting',
-        meta: {
-          title: t('router.personalSetting'),
-          hidden: true,
-          canTo: true
-        }
       }
     ]
   },

+ 137 - 30
src/views/Personal/PersonalCenter/PersonalCenter.vue

@@ -1,41 +1,148 @@
-<script lang="ts" setup>
-import { ElCard, ElTag } from 'element-plus'
-import avatar from '@/assets/imgs/avatar.jpg'
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { ref, unref } from 'vue'
+import { ElDivider, ElImage, ElTag, ElTabPane, ElTabs, ElButton, ElMessage } from 'element-plus'
+import defaultAvatar from '@/assets/imgs/avatar.jpg'
+import UploadAvatar from './components/UploadAvatar.vue'
+import { Dialog } from '@/components/Dialog'
+import EditInfo from './components/EditInfo.vue'
+import EditPassword from './components/EditPassword.vue'
+
+const userInfo = ref()
+const fetchDetailUserApi = async () => {
+  // 这里可以调用接口获取用户信息
+  const data = {
+    id: 1,
+    username: 'admin',
+    realName: 'admin',
+    phoneNumber: '18888888888',
+    email: '502431556@qq.com',
+    avatarUrl: '',
+    roleList: ['超级管理员']
+  }
+  userInfo.value = data
+}
+fetchDetailUserApi()
+
+const activeName = ref('first')
+
+const dialogVisible = ref(false)
+
+const uploadAvatarRef = ref<ComponentRef<typeof UploadAvatar>>()
+const avatarLoading = ref(false)
+const saveAvatar = async () => {
+  try {
+    avatarLoading.value = true
+    const base64 = unref(uploadAvatarRef)?.getBase64()
+    console.log(base64)
+    // 这里可以调用修改头像接口
+    fetchDetailUserApi()
+    ElMessage.success('修改成功')
+    dialogVisible.value = false
+  } catch (error) {
+    console.log(error)
+  } finally {
+    avatarLoading.value = false
+  }
+}
 </script>
 
 <template>
-  <div>
-    <ElCard
-      shadow="hover"
-      class="border-none! h-200px relative"
-      :body-style="{
-        padding: '0'
-      }"
-    >
-      <div class="card h-140px"> </div>
-      <img
-        :src="avatar"
-        alt="avatar"
-        class="w-128px h-128px border-rd-50% bottom-[40px] left-20px absolute"
-      />
-      <div class="absolute left-168px bottom-70px">
-        <div class="font-size-24px font-600 mb-10px color-#fff">Archer</div>
-        <div class="color-#A3A6AD"> 种一棵树最好的时间是十年前,其次是现在。 </div>
+  <div class="flex w-100% h-100%">
+    <ContentWrap title="个人信息" class="w-400px">
+      <div class="flex justify-center items-center">
+        <div
+          class="avatar w-[150px] h-[150px] relative cursor-pointer"
+          @click="dialogVisible = true"
+        >
+          <ElImage
+            class="w-[150px] h-[150px] rounded-full"
+            :src="userInfo?.avatarUrl || defaultAvatar"
+            fit="fill"
+          />
+        </div>
+      </div>
+      <ElDivider />
+      <div class="flex justify-between items-center">
+        <div>账号:</div>
+        <div>{{ userInfo?.username }}</div>
       </div>
-      <div class="h-60px flex items-center justify-end pr-20px">
-        <ElTag class="mr-10px">前端开发</ElTag>
-        <ElTag class="mr-10px">专注设计</ElTag>
-        <ElTag class="mr-10px">很有想法</ElTag>
-        <ElTag>时间管理</ElTag>
+      <ElDivider />
+      <div class="flex justify-between items-center">
+        <div>昵称:</div>
+        <div>{{ userInfo?.realName }}</div>
       </div>
-    </ElCard>
+      <ElDivider />
+      <div class="flex justify-between items-center">
+        <div>手机号码:</div>
+        <div>{{ userInfo?.phoneNumber ?? '-' }}</div>
+      </div>
+      <ElDivider />
+      <div class="flex justify-between items-center">
+        <div>用户邮箱:</div>
+        <div>{{ userInfo?.email ?? '-' }}</div>
+      </div>
+      <ElDivider />
+      <div class="flex justify-between items-center">
+        <div>所属角色:</div>
+        <div>
+          <template v-if="userInfo?.roleList?.length">
+            <ElTag v-for="item in userInfo?.roleList || []" :key="item" class="ml-2 mb-w"
+              >{{ item }}
+            </ElTag>
+          </template>
+          <template v-else>-</template>
+        </div>
+      </div>
+      <ElDivider />
+    </ContentWrap>
+    <ContentWrap title="基本资料" class="flex-[3] ml-20px">
+      <ElTabs v-model="activeName">
+        <ElTabPane label="基本信息" name="first">
+          <EditInfo :user-info="userInfo" />
+        </ElTabPane>
+        <ElTabPane label="修改密码" name="second">
+          <EditPassword />
+        </ElTabPane>
+      </ElTabs>
+    </ContentWrap>
   </div>
+
+  <Dialog v-model="dialogVisible" title="修改头像" width="800px">
+    <UploadAvatar ref="uploadAvatarRef" :url="userInfo?.avatarUrl || defaultAvatar" />
+
+    <template #footer>
+      <ElButton type="primary" :loading="avatarLoading" @click="saveAvatar"> 保存 </ElButton>
+      <ElButton @click="dialogVisible = false">关闭</ElButton>
+    </template>
+  </Dialog>
 </template>
 
 <style lang="less" scoped>
-.card {
-  background-image: url("~'@/assets/imgs/personal-center-bg.jpg'");
-  background-position: center;
-  background-size: cover;
+.avatar {
+  position: relative;
+
+  &::after {
+    position: absolute;
+    top: 0;
+    left: 0;
+    display: flex;
+    width: 100%;
+    height: 100%;
+    font-size: 50px;
+    color: #fff;
+    background-color: rgb(0 0 0 / 40%);
+    border-radius: 50%;
+    content: '+';
+    opacity: 0;
+    justify-content: center;
+    align-items: center;
+  }
+
+  &:hover {
+    &::after {
+      opacity: 1;
+    }
+  }
 }
 </style>

+ 96 - 0
src/views/Personal/PersonalCenter/components/EditInfo.vue

@@ -0,0 +1,96 @@
+<script lang="ts" setup>
+import { FormSchema, Form } from '@/components/Form'
+import { useForm } from '@/hooks/web/useForm'
+import { useValidator } from '@/hooks/web/useValidator'
+import { reactive, ref, watch } from 'vue'
+import { ElDivider, ElMessage, ElMessageBox } from 'element-plus'
+
+const props = defineProps({
+  userInfo: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+const { required, phone, maxlength, email } = useValidator()
+
+const formSchema = reactive<FormSchema[]>([
+  {
+    field: 'realName',
+    label: '昵称',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'phoneNumber',
+    label: '手机号码',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'email',
+    label: '邮箱',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  }
+])
+
+const rules = reactive({
+  realName: [required(), maxlength(50)],
+  phoneNumber: [phone()],
+  email: [email()]
+})
+
+const { formRegister, formMethods } = useForm()
+const { setValues, getElFormExpose } = formMethods
+
+watch(
+  () => props.userInfo,
+  (value) => {
+    setValues(value)
+  },
+  {
+    immediate: true,
+    deep: true
+  }
+)
+
+const saveLoading = ref(false)
+const save = async () => {
+  const elForm = await getElFormExpose()
+  const valid = await elForm?.validate().catch((err) => {
+    console.log(err)
+  })
+  if (valid) {
+    ElMessageBox.confirm('是否确认修改?', '提示', {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+      .then(async () => {
+        try {
+          saveLoading.value = true
+          // 这里可以调用修改用户信息接口
+          ElMessage.success('修改成功')
+        } catch (error) {
+          console.log(error)
+        } finally {
+          saveLoading.value = false
+        }
+      })
+      .catch(() => {})
+  }
+}
+</script>
+
+<template>
+  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+  <ElDivider />
+  <BaseButton type="primary" @click="save">保存</BaseButton>
+</template>

+ 110 - 0
src/views/Personal/PersonalCenter/components/EditPassword.vue

@@ -0,0 +1,110 @@
+<script setup lang="ts">
+import { Form, FormSchema } from '@/components/Form'
+import { useForm } from '@/hooks/web/useForm'
+import { reactive, ref } from 'vue'
+import { useValidator } from '@/hooks/web/useValidator'
+import { ElMessage, ElMessageBox, ElDivider } from 'element-plus'
+
+const { required } = useValidator()
+
+const formSchema = reactive<FormSchema[]>([
+  {
+    field: 'password',
+    label: '旧密码',
+    component: 'InputPassword',
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'newPassword',
+    label: '新密码',
+    component: 'InputPassword',
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      strength: true
+    }
+  },
+  {
+    field: 'newPassword2',
+    label: '确认新密码',
+    component: 'InputPassword',
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      strength: true
+    }
+  }
+])
+
+const rules = reactive({
+  password: [required()],
+  newPassword: [
+    required(),
+    {
+      asyncValidator: async (_, val, callback) => {
+        const formData = await getFormData()
+        const { newPassword2 } = formData
+        if (val !== newPassword2) {
+          callback(new Error('新密码与确认新密码不一致'))
+        } else {
+          callback()
+        }
+      }
+    }
+  ],
+  newPassword2: [
+    required(),
+    {
+      asyncValidator: async (_, val, callback) => {
+        const formData = await getFormData()
+        const { newPassword } = formData
+        if (val !== newPassword) {
+          callback(new Error('确认新密码与新密码不一致'))
+        } else {
+          callback()
+        }
+      }
+    }
+  ]
+})
+
+const { formRegister, formMethods } = useForm()
+const { getFormData, getElFormExpose } = formMethods
+
+const saveLoading = ref(false)
+const save = async () => {
+  const elForm = await getElFormExpose()
+  const valid = await elForm?.validate().catch((err) => {
+    console.log(err)
+  })
+  if (valid) {
+    ElMessageBox.confirm('是否确认修改?', '提示', {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+      .then(async () => {
+        try {
+          saveLoading.value = true
+          // 这里可以调用修改密码的接口
+          ElMessage.success('修改成功')
+        } catch (error) {
+          console.log(error)
+        } finally {
+          saveLoading.value = false
+        }
+      })
+      .catch(() => {})
+  }
+}
+</script>
+
+<template>
+  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+  <ElDivider />
+  <BaseButton type="primary" @click="save">确认修改</BaseButton>
+</template>

+ 30 - 0
src/views/Personal/PersonalCenter/components/UploadAvatar.vue

@@ -0,0 +1,30 @@
+<script setup lang="ts">
+import { ImageCropping } from '@/components/ImageCropping'
+import { ref, unref } from 'vue'
+
+defineProps({
+  url: {
+    type: String,
+    default: ''
+  }
+})
+
+const fileUrl = ref('')
+
+const CropperRef = ref<ComponentRef<typeof ImageCropping>>()
+
+const getBase64 = () => {
+  const base64 = unref(CropperRef)?.cropperExpose?.getCroppedCanvas()?.toDataURL() ?? ''
+  return base64
+}
+
+defineExpose({
+  getBase64
+})
+</script>
+
+<template>
+  <div>
+    <ImageCropping ref="CropperRef" :image-url="fileUrl || url" />
+  </div>
+</template>

+ 0 - 7
src/views/Personal/PersonalSetting/PersonalSetting.vue

@@ -1,7 +0,0 @@
-<script lang="ts" setup></script>
-
-<template>
-  <div>
-    <h1>个人设置</h1>
-  </div>
-</template>