feat: 新增个人中心页

This commit is contained in:
kailong321200875 2024-02-28 14:38:59 +08:00
parent 5f8e795f64
commit 4146716655
10 changed files with 410 additions and 55 deletions

View File

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

View File

@ -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 { return {
required, required,
lengthRange, lengthRange,
notSpace, notSpace,
notSpecialCharacters notSpecialCharacters,
phone,
email,
maxlength
} }
} }

View File

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

View File

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

View File

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

View File

@ -1,41 +1,148 @@
<script lang="ts" setup> <script setup lang="ts">
import { ElCard, ElTag } from 'element-plus' import { ContentWrap } from '@/components/ContentWrap'
import avatar from '@/assets/imgs/avatar.jpg' 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> </script>
<template> <template>
<div> <div class="flex w-100% h-100%">
<ElCard <ContentWrap title="个人信息" class="w-400px">
shadow="hover" <div class="flex justify-center items-center">
class="border-none! h-200px relative" <div
:body-style="{ class="avatar w-[150px] h-[150px] relative cursor-pointer"
padding: '0' @click="dialogVisible = true"
}" >
> <ElImage
<div class="card h-140px"> </div> class="w-[150px] h-[150px] rounded-full"
<img :src="userInfo?.avatarUrl || defaultAvatar"
:src="avatar" fit="fill"
alt="avatar" />
class="w-128px h-128px border-rd-50% bottom-[40px] left-20px absolute" </div>
/>
<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> </div>
<div class="h-60px flex items-center justify-end pr-20px"> <ElDivider />
<ElTag class="mr-10px">前端开发</ElTag> <div class="flex justify-between items-center">
<ElTag class="mr-10px">专注设计</ElTag> <div>账号</div>
<ElTag class="mr-10px">很有想法</ElTag> <div>{{ userInfo?.username }}</div>
<ElTag>时间管理</ElTag>
</div> </div>
</ElCard> <ElDivider />
<div class="flex justify-between items-center">
<div>昵称</div>
<div>{{ userInfo?.realName }}</div>
</div>
<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> </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> </template>
<style lang="less" scoped> <style lang="less" scoped>
.card { .avatar {
background-image: url("~'@/assets/imgs/personal-center-bg.jpg'"); position: relative;
background-position: center;
background-size: cover; &::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> </style>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

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