更新个人中心页面

This commit is contained in:
洛洛希雅Lolosia 2023-06-25 14:02:24 +08:00
parent e072be8dcb
commit a42a307a81
9 changed files with 392 additions and 47 deletions

View File

@ -22,10 +22,6 @@
"elLoading": true, "elLoading": true,
"elMessage": true, "elMessage": true,
"elNotify": true, "elNotify": true,
"filterAsyncRouter": true,
"filterAsyncRouterByCodes": true,
"filterAsyncRoutesByMenuList": true,
"filterAsyncRoutesByRoles": true,
"freshRouter": true, "freshRouter": true,
"getCurrentInstance": true, "getCurrentInstance": true,
"getCurrentScope": true, "getCurrentScope": true,
@ -74,6 +70,7 @@
"routerBack": true, "routerBack": true,
"routerPush": true, "routerPush": true,
"routerReplace": true, "routerReplace": true,
"setRouterFromDatabase": true,
"shallowReactive": true, "shallowReactive": true,
"shallowReadonly": true, "shallowReadonly": true,
"shallowRef": true, "shallowRef": true,

View File

@ -47,13 +47,17 @@ export async function getUser(idList): Promise<IUser[]> {
return post('/user/get', { idList }) return post('/user/get', { idList })
} }
export async function editUser(data: IUser) {
return post('/user/edit', data)
}
/** /**
* *
* @param keys * @param keys
* @return {Promise<any[]>} * @return {Promise<any[]>}
*/ */
export function userSearching(keys) { export function userSearching(keys) {
return post('user/searching', { keys }) return post('/user/searching', { keys })
} }
//修改密码 //修改密码

View File

@ -4,6 +4,7 @@ import { defineStore } from 'pinia'
import type { RouterTypes } from '~/basic' import type { RouterTypes } from '~/basic'
import defaultSettings from '@/settings' import defaultSettings from '@/settings'
import router, { constantRoutes } from '@/router' import router, { constantRoutes } from '@/router'
import type { IUser } from '@/api/user'
export const useBasicStore = defineStore('basic', { export const useBasicStore = defineStore('basic', {
state: () => { state: () => {
@ -129,3 +130,23 @@ export const useBasicStore = defineStore('basic', {
} }
} }
}) })
export interface IUserInfo {
userName: string
realName: string
avatar: string
phone: string
id: string
isUse: boolean
team: string
iGameKey: string
}
export interface IDatabaseType {
createdAt: string
updatedAt: string
createdBy?: string
updatedBy?: string
}
export interface IUnionUserInfo extends IUserInfo, IUser, IDatabaseType {}

View File

@ -0,0 +1,180 @@
<template>
<el-card class="user-detail el-descriptions">
<template #header>
<div class="user-detail-header">
<span class="user-detail-title">个人信息</span>
<el-button v-if="!editing" type="primary" @click="editing=true">修改</el-button>
<template v-else>
<el-button type="warning" @click="cancel">取消</el-button>
<el-button type="primary" @click="save">保存</el-button>
</template>
</div>
</template>
<table class="user-table" :class="{'edit-mode': editing}">
<user-detail-body ref="body">
<user-detail-avatar-item />
<user-detail-item label="用户名" prop="realName" />
<user-detail-item label="账号" prop="userName" :editable="false" />
<user-detail-item label="手机号" prop="phone" />
<user-detail-password-item />
<user-detail-item label="UID" prop="id" :editable="false" />
<user-detail-item label="注册日期" prop="createdAt" :editable="false" />
<user-detail-item label="上次更新" prop="updatedAt" :editable="false" />
</user-detail-body>
</table>
</el-card>
</template>
<script lang="ts">
import UserDetailItem from "@/views/user/profile/components/UserDetailItem.vue";
import UserDetailAvatarItem from "@/views/user/profile/components/UserDetailAvatarItem.vue";
import UserDetailPasswordItem from "@/views/user/profile/components/UserDetailPasswordItem.vue";
import { editUser } from "@/api/user";
import { ElMessage } from "element-plus";
import type { VNode } from "vue";
export default defineComponent({
components: {
UserDetailBody: {
render() {
return h("tbody", this.$slots.default());
}
},
UserDetailItem,
UserDetailAvatarItem,
UserDetailPasswordItem
},
setup() {
const userInfo = toRef(useBasicStore(), "userInfo");
const editing = ref(false);
const copy = ref(JSON.parse(JSON.stringify(userInfo.value)));
provide("userDetail:editing", editing);
provide("userDetail:user", copy);
const body = ref();
async function save() {
try {
const vNodes = body.value.$.subTree.children as VNode[];
const component = vNodes.map(it => it.component!);
for (const c of component) {
const rs = c.exposed?.ok?.();
if (rs === false) {
ElMessage.error("请检查输入是否正确");
return;
}
}
} catch (e: Error | any) {
ElMessage.error(e.message);
}
await editUser({
...copy.value,
avatar: undefined
});
ElMessage.success("操作成功");
userInfo.value = copy.value;
delete copy.password;
cancel();
}
function cancel() {
copy.value = JSON.parse(JSON.stringify(userInfo.value));
editing.value = false;
}
return {
cancel, save, editing, body
};
}
});
</script>
<style scoped lang="scss">
.user-detail {
.user-detail-header {
display: flex;
align-items: center;
}
.user-detail-title {
flex-grow: 1;
}
.user-table {
border-collapse: collapse;
width: 100%;
&.edit-mode :deep(.item-content) {
color: lightgray;
}
&.edit-mode :deep(tr:not(.editing) .item-title) {
color: lightgray;
}
&.edit-mode :deep(tr.editing .item-content) {
box-shadow: none;
transition-property: box-shadow;
transition-duration: 0.5s;
border-radius: 7px;
&:has(input) {
cursor: text;
}
&:not(:focus-within):hover {
box-shadow: 0 0 3px var(--el-color-primary) inset;
}
}
:deep(.item-title) {
width: 128px;
background: var(--el-descriptions-item-bordered-label-background);
font-weight: 700;
color: var(--el-text-color-regular);
}
:deep(td) {
padding: 0.8em;
border: var(--el-descriptions-table-border);
}
:deep(.item-input) {
width: 100%;
position: relative;
&::after {
content: '';
display: block;
/*开始时候下划线的宽度为100%*/
width: 100%;
height: 2px;
position: absolute;
bottom: -3px;
background-color: var(--el-color-primary);
transition: all 0.3s ease-in-out;
/*通过transform的缩放scale来让初始时x轴为0*/
transform: scale3d(0, 1, 1);
/*将坐标原点移到元素的中间,以原点为中心进行缩放*/
transform-origin: 50% 0;
}
&:focus-within::after {
/*内部取得焦点时显示下划线*/
transform: scale3d(1, 1, 1);
}
input {
border: none;
width: 100%;
outline: none;
}
}
}
}
</style>

View File

@ -0,0 +1,80 @@
<template>
<tr :class="{editing}">
<td class="item-title">头像</td>
<td class="item-content">
<div v-if="!editing" class="avatar-frame">
<img :src="user.avatar || UserImage" class="avatar" alt="用户头像" />
</div>
<div v-else class="avatar-frame">
<img :src="user.avatar || UserImage" class="avatar" alt="用户头像" />
<div class="avatar-tip" @click="showAvatarEditor = true">
<i class="bi bi-pen-fill" />
<span>修改头像</span>
</div>
</div>
<avatar-edit v-model="showAvatarEditor" />
</td>
</tr>
</template>
<script lang="ts" setup>
import UserImage from "@/assets/layout/user.png";
import type { IUnionUserInfo } from "@/store/basic";
import type { Ref } from "vue";
import AvatarEdit from "@/views/user/profile/components/AvatarEditor.vue";
const showAvatarEditor = ref(false);
const user: Ref<IUnionUserInfo> = inject("userDetail:user") as any;
const editing: Ref<boolean> = inject("userDetail:editing") as any;
const userInfo = toRef(useBasicStore(), "userInfo");
watch(() => userInfo.value.avatar, it => {
user.value.avatar = it || "";
});
defineExpose({
ok: () => true
});
</script>
<style scoped lang="scss">
.avatar-frame {
position: relative;
height: 96px;
width: 96px;
overflow: hidden;
border-radius: 24%;
&:not(:hover) .avatar-tip {
display: none;
}
.avatar {
height: 96px;
width: 96px;
pointer-events: none;
}
.avatar-tip {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #88888888;
color: white;
user-select: none;
cursor: pointer;
.bi {
font-size: 2em;
}
}
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<tr :class="{editing: editing && props.editable}">
<td class="item-title">{{ props.label ?? props.prop }}</td>
<td class="item-content">
<div v-if="editing && props.editable" class="item-input">
<input v-model="user[props.prop]" />
</div>
<template v-else>{{ user[props.prop] }}</template>
</td>
</tr>
</template>
<script lang="ts" setup>
import type { IUnionUserInfo } from "@/store/basic";
import type { Ref } from "vue";
const props = withDefaults(defineProps<{
prop: keyof IUnionUserInfo,
editable?: boolean
label?: string
}>(), {
editable: true
});
const user: Ref<IUnionUserInfo> = inject("userDetail:user") as any;
const editing: Ref<boolean> = inject("userDetail:editing") as any;
defineExpose({
ok: () => true
});
</script>
<style scoped>
</style>

View File

@ -0,0 +1,66 @@
<template>
<tr v-if="editing" :class="{editing}">
<td class="item-title">密码</td>
<td class="item-content">
<div class="item-input">
<input v-model="passwords.a" type="password" placeholder="若要修改密码,请在此处输入" />
</div>
</td>
</tr>
<tr v-if="editing && passwords.a" :class="{editing}">
<td class="item-title">再输入一遍密码</td>
<td class="item-content">
<div class="item-input" :class="{error}">
<input v-model="passwords.b" type="password" />
</div>
<span v-if="error" class="tip">两次输入的密码不一致</span>
</td>
</tr>
</template>
<script lang="ts" setup>
import type { IUnionUserInfo } from "@/store/basic";
import type { Ref } from "vue";
const user: Ref<IUnionUserInfo> = inject("userDetail:user") as any;
const editing: Ref<boolean> = inject("userDetail:editing") as any;
const passwords = reactive({
a: "",
b: ""
});
const error = computed(() => {
return passwords.a && passwords.b && (passwords.a !== passwords.b);
});
defineExpose({
ok() {
if (passwords.a) {
if (passwords.a != passwords.b) {
throw new Error("两次输入的密码不一致");
}
user.value.password = passwords.a;
return true;
}
return true;
}
});
</script>
<style scoped>
.item-content {
.error::after {
background-color: red;
}
.tip {
font-size: 0.8em;
bottom: 3px;
color: red;
//text-shadow: 1px 1px 1px darkred;
}
}
</style>

View File

@ -1,47 +1,11 @@
<template> <template>
<div class="user-profile"> <div class="user-profile">
<el-card header="个人信息"> <user-detail />
<el-descriptions label-width="120px" size="default" border :column="1">
<el-descriptions-item label="头像">
<div class="avatar-frame">
<img :src="userInfo.avatar || UserImage" class="avatar" alt="用户头像" @click="showAvatarEditor = true" />
<div class="avatar-tip">
<i class="bi bi-pen-fill" />
<span>修改头像</span>
</div>
</div>
</el-descriptions-item>
<el-descriptions-item label="用户名">
{{ userInfo.realName }}
</el-descriptions-item>
<el-descriptions-item label="账号">
{{ userInfo.userName }}
</el-descriptions-item>
<el-descriptions-item label="手机号">
{{ userInfo.phone }}
</el-descriptions-item>
<el-descriptions-item label="UID">
{{ userInfo.id }}
</el-descriptions-item>
<el-descriptions-item label="注册日期">
{{ userInfo.createdAt }}
</el-descriptions-item>
<el-descriptions-item label="上次更新">
{{ userInfo.updatedAt }}
</el-descriptions-item>
</el-descriptions>
</el-card>
<avatar-edit v-model="showAvatarEditor" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import AvatarEdit from "@/views/user/profile/components/AvatarEditor.vue"; import UserDetail from "@/views/user/profile/components/UserDetail.vue";
import UserImage from "@/assets/layout/user.png";
const showAvatarEditor = ref(false);
const userInfo = toRef(useBasicStore(), "userInfo");
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -23,10 +23,6 @@ declare global {
const elLoading: typeof import('../src/hooks/use-element')['elLoading'] const elLoading: typeof import('../src/hooks/use-element')['elLoading']
const elMessage: typeof import('../src/hooks/use-element')['elMessage'] const elMessage: typeof import('../src/hooks/use-element')['elMessage']
const elNotify: typeof import('../src/hooks/use-element')['elNotify'] const elNotify: typeof import('../src/hooks/use-element')['elNotify']
const filterAsyncRouter: typeof import('../src/hooks/use-permission')['filterAsyncRouter']
const filterAsyncRouterByCodes: typeof import('../src/hooks/use-permission')['filterAsyncRouterByCodes']
const filterAsyncRoutesByMenuList: typeof import('../src/hooks/use-permission')['filterAsyncRoutesByMenuList']
const filterAsyncRoutesByRoles: typeof import('../src/hooks/use-permission')['filterAsyncRoutesByRoles']
const freshRouter: typeof import('../src/hooks/use-permission')['freshRouter'] const freshRouter: typeof import('../src/hooks/use-permission')['freshRouter']
const getCurrentInstance: typeof import('vue')['getCurrentInstance'] const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope'] const getCurrentScope: typeof import('vue')['getCurrentScope']
@ -75,6 +71,7 @@ declare global {
const routerBack: typeof import('../src/hooks/use-self-router')['routerBack'] const routerBack: typeof import('../src/hooks/use-self-router')['routerBack']
const routerPush: typeof import('../src/hooks/use-self-router')['routerPush'] const routerPush: typeof import('../src/hooks/use-self-router')['routerPush']
const routerReplace: typeof import('../src/hooks/use-self-router')['routerReplace'] const routerReplace: typeof import('../src/hooks/use-self-router')['routerReplace']
const setRouterFromDatabase: typeof import('../src/hooks/use-permission')['setRouterFromDatabase']
const shallowReactive: typeof import('vue')['shallowReactive'] const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly'] const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef'] const shallowRef: typeof import('vue')['shallowRef']