更新个人中心页面
This commit is contained in:
parent
e072be8dcb
commit
a42a307a81
|
@ -22,10 +22,6 @@
|
|||
"elLoading": true,
|
||||
"elMessage": true,
|
||||
"elNotify": true,
|
||||
"filterAsyncRouter": true,
|
||||
"filterAsyncRouterByCodes": true,
|
||||
"filterAsyncRoutesByMenuList": true,
|
||||
"filterAsyncRoutesByRoles": true,
|
||||
"freshRouter": true,
|
||||
"getCurrentInstance": true,
|
||||
"getCurrentScope": true,
|
||||
|
@ -74,6 +70,7 @@
|
|||
"routerBack": true,
|
||||
"routerPush": true,
|
||||
"routerReplace": true,
|
||||
"setRouterFromDatabase": true,
|
||||
"shallowReactive": true,
|
||||
"shallowReadonly": true,
|
||||
"shallowRef": true,
|
||||
|
|
|
@ -47,13 +47,17 @@ export async function getUser(idList): Promise<IUser[]> {
|
|||
return post('/user/get', { idList })
|
||||
}
|
||||
|
||||
export async function editUser(data: IUser) {
|
||||
return post('/user/edit', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照文本搜索前十条用户
|
||||
* @param keys
|
||||
* @return {Promise<any[]>}
|
||||
*/
|
||||
export function userSearching(keys) {
|
||||
return post('user/searching', { keys })
|
||||
return post('/user/searching', { keys })
|
||||
}
|
||||
|
||||
//修改密码
|
||||
|
|
|
@ -4,6 +4,7 @@ import { defineStore } from 'pinia'
|
|||
import type { RouterTypes } from '~/basic'
|
||||
import defaultSettings from '@/settings'
|
||||
import router, { constantRoutes } from '@/router'
|
||||
import type { IUser } from '@/api/user'
|
||||
|
||||
export const useBasicStore = defineStore('basic', {
|
||||
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 {}
|
||||
|
|
180
src/views/user/profile/components/UserDetail.vue
Normal file
180
src/views/user/profile/components/UserDetail.vue
Normal 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>
|
80
src/views/user/profile/components/UserDetailAvatarItem.vue
Normal file
80
src/views/user/profile/components/UserDetailAvatarItem.vue
Normal 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>
|
36
src/views/user/profile/components/UserDetailItem.vue
Normal file
36
src/views/user/profile/components/UserDetailItem.vue
Normal 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>
|
66
src/views/user/profile/components/UserDetailPasswordItem.vue
Normal file
66
src/views/user/profile/components/UserDetailPasswordItem.vue
Normal 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>
|
|
@ -1,47 +1,11 @@
|
|||
<template>
|
||||
<div class="user-profile">
|
||||
<el-card header="个人信息">
|
||||
<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" />
|
||||
<user-detail />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AvatarEdit from "@/views/user/profile/components/AvatarEditor.vue";
|
||||
import UserImage from "@/assets/layout/user.png";
|
||||
|
||||
const showAvatarEditor = ref(false);
|
||||
const userInfo = toRef(useBasicStore(), "userInfo");
|
||||
|
||||
import UserDetail from "@/views/user/profile/components/UserDetail.vue";
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
5
typings/auto-imports.d.ts
vendored
5
typings/auto-imports.d.ts
vendored
|
@ -23,10 +23,6 @@ declare global {
|
|||
const elLoading: typeof import('../src/hooks/use-element')['elLoading']
|
||||
const elMessage: typeof import('../src/hooks/use-element')['elMessage']
|
||||
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 getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
|
@ -75,6 +71,7 @@ declare global {
|
|||
const routerBack: typeof import('../src/hooks/use-self-router')['routerBack']
|
||||
const routerPush: typeof import('../src/hooks/use-self-router')['routerPush']
|
||||
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 shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
|
|
Loading…
Reference in New Issue
Block a user