feat: 新增个人中心页
This commit is contained in:
parent
5f8e795f64
commit
4146716655
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -182,7 +182,6 @@ export default {
|
||||||
tableVideoPreview: '表格视频预览',
|
tableVideoPreview: '表格视频预览',
|
||||||
cardTable: '卡片表格',
|
cardTable: '卡片表格',
|
||||||
personalCenter: '个人中心',
|
personalCenter: '个人中心',
|
||||||
personalSetting: '个人设置',
|
|
||||||
personal: '个人'
|
personal: '个人'
|
||||||
},
|
},
|
||||||
permission: {
|
permission: {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -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"
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<div class="card h-140px"> </div>
|
<ElImage
|
||||||
<img
|
class="w-[150px] h-[150px] rounded-full"
|
||||||
:src="avatar"
|
:src="userInfo?.avatarUrl || defaultAvatar"
|
||||||
alt="avatar"
|
fit="fill"
|
||||||
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>
|
</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>
|
|
||||||
</div>
|
</div>
|
||||||
</ElCard>
|
<ElDivider />
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div>账号:</div>
|
||||||
|
<div>{{ userInfo?.username }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,7 +0,0 @@
|
||||||
<script lang="ts" setup></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h1>个人设置</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
Loading…
Reference in New Issue