feat: 个人信息页面.

This commit is contained in:
lijiahang
2023-10-31 19:07:48 +08:00
parent 6c9aabd4fd
commit 3fef9b8ae2
42 changed files with 647 additions and 148 deletions

View File

@@ -0,0 +1,131 @@
<template>
<a-spin :loading="loading" class="main-container">
<span class="extra-message">只展示最近登录的 30 条历史记录</span>
<a-timeline>
<a-timeline-item v-for="item in list"
:key="item.id">
<!-- 图标 -->
<template #dot>
<div class="icon-container">
<icon-desktop />
</div>
</template>
<!-- 日志行 -->
<div class="log-line">
<!-- 地址行 -->
<span class="address-line">
<span class="mr8">{{ item.address }}</span>
<span>{{ item.location }}</span>
</span>
<!-- 错误信息行 -->
<span class="error-line" v-if="item.result === ResultStatus.FAILED">
登录失败: {{ item.errorMessage }}
</span>
<!-- 时间行 -->
<span class="time-line">
{{ dateFormat(new Date(item.createTime)) }}
</span>
<!-- ua -->
<span class="ua-line">
{{ item.userAgent }}
</span>
</div>
</a-timeline-item>
</a-timeline>
</a-spin>
</template>
<script lang="ts">
export default {
name: 'login-history'
};
</script>
<script lang="ts" setup>
import type { LoginHistoryQueryResponse } from '@/api/user/operator-log';
import useLoading from '@/hooks/loading';
import { ref, onMounted } from 'vue';
import { useUserStore } from '@/store';
import { ResultStatus } from '../types/const';
import { getCurrentLoginHistory } from '@/api/user/operator-log';
import { dateFormat } from '@/utils';
const list = ref<LoginHistoryQueryResponse[]>([]);
const userStore = useUserStore();
const { loading, setLoading } = useLoading();
// 查询操作日志
onMounted(async () => {
try {
setLoading(true);
const { data } = await getCurrentLoginHistory();
list.value = data;
} catch (e) {
} finally {
setLoading(false);
}
});
</script>
<style lang="less" scoped>
.main-container {
width: 100%;
min-height: 200px;
padding-left: 48px;
}
.extra-message {
margin-bottom: 38px;
margin-left: -20px;
display: block;
color: var(--color-text-3);
user-select: none;
}
.icon-container {
border-radius: 50%;
width: 56px;
height: 56px;
background: var(--color-fill-4);
font-size: 28px;
color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
}
:deep(.arco-timeline-item-content-wrapper) {
position: relative;
margin-left: 44px;
margin-top: -22px;
}
:deep(.arco-timeline-item) {
padding-bottom: 36px;
}
.log-line {
display: flex;
flex-direction: column;
.address-line {
color: var(--color-text-1);
font-size: 16px;
font-weight: 600;
}
.time-line, .ua-line, .error-line {
color: var(--color-text-3);
font-size: 14px;
margin-top: 2px;
}
.error-line {
color: rgb(var(--danger-6));
font-weight: 600;
}
}
</style>

View File

@@ -0,0 +1,112 @@
<template>
<a-spin :loading="loading" style="width: 400px;">
<!-- 头像 -->
<div class="avatar-container">
<div class="avatar-wrapper">
<a-avatar :size="88"
:style="{ backgroundColor: '#3370ff' }">
{{ nickname }}
</a-avatar>
</div>
</div>
<a-form :model="formModel"
ref="formRef"
label-align="right"
size="medium"
:style="{ width: '100%' }"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
:rules="formRules">
<!-- 用户名 -->
<a-form-item field="username" label="用户名">
<a-input v-model="formModel.username" disabled />
</a-form-item>
<!-- 花名 -->
<a-form-item field="nickname" label="花名">
<a-input v-model="formModel.nickname" placeholder="请输入花名" />
</a-form-item>
<!-- 手机号 -->
<a-form-item field="mobile" label="手机号">
<a-input v-model="formModel.mobile" placeholder="请输入手机号" />
</a-form-item>
<!-- 邮箱 -->
<a-form-item field="email" label="邮箱">
<a-input v-model="formModel.email" placeholder="请输入邮箱" />
</a-form-item>
</a-form>
<!-- 操作 -->
<div class="handler-container">
<a-button type="primary" @click="save">保存</a-button>
</div>
</a-spin>
</template>
<script lang="ts">
export default {
name: 'user-info'
};
</script>
<script lang="ts" setup>
import type { UserUpdateRequest } from '@/api/user/user';
import useLoading from '@/hooks/loading';
import { computed, ref, onMounted } from 'vue';
import formRules from '../../user/types/form.rules';
import { useUserStore } from '@/store';
import { getCurrentUser, updateCurrentUser } from '@/api/user/user';
import { pick } from 'lodash';
const userStore = useUserStore();
const { loading, setLoading } = useLoading();
const formRef = ref();
const formModel = ref<UserUpdateRequest>({});
// 用户名
const nickname = computed(() => userStore.nickname?.substring(0, 1));
// 保存
const save = async () => {
setLoading(true);
try {
await updateCurrentUser(formModel.value);
userStore.nickname = formModel.value.nickname;
} catch (e) {
} finally {
setLoading(false);
}
};
// 加载用户信息
onMounted(async () => {
setLoading(true);
try {
const { data } = await getCurrentUser();
formModel.value = pick(data, 'id', 'username', 'nickname', 'mobile', 'email');
} catch (e) {
} finally {
setLoading(false);
}
});
</script>
<style lang="less" scoped>
.avatar-container {
display: flex;
justify-content: flex-end;
padding: 4px 0;
.avatar-wrapper {
display: flex;
justify-content: center;
margin-bottom: 16px;
width: calc(100% / 24 * 18);
}
}
.handler-container {
display: flex;
margin-left: calc(100% / 24 * 6);
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<div class="tabs-container">
<a-tabs type="rounded"
size="medium"
position="left"
:lazy-load="true"
:destroy-on-hide="true">
<!-- 个人信息 -->
<a-tab-pane key="1" title="个人信息">
<user-info />
</a-tab-pane>
<!-- 登录日志 -->
<a-tab-pane key="2" title="登录日志">
<login-history />
</a-tab-pane>
<!-- 登录设备 -->
<a-tab-pane key="3" title="登录设备">
<login-history />
</a-tab-pane>
<!-- 操作日志 -->
<a-tab-pane key="4" title="操作日志">
</a-tab-pane>
</a-tabs>
</div>
</template>
<script lang="ts">
export default {
name: 'userInfo'
};
</script>
<script lang="ts" setup>
import UserInfo from './components/user-info.vue';
import LoginHistory from './components/login-history.vue';
</script>
<style lang="less" scoped>
.tabs-container {
background: #FFFFFF;
margin: 16px 16px 0 16px;
padding: 16px;
display: flex;
flex-direction: column;
border-radius: 4px;
}
:deep(.arco-tabs-pane) {
border-left: 1px var(--color-neutral-3) solid;
}
:deep(.arco-tabs-tab-title) {
user-select: none;
}
</style>

View File

@@ -0,0 +1,7 @@
// 结果状态
export const ResultStatus = {
// 失败
FAILED: 0,
// 成功
SUCCESS: 1,
};

View File

@@ -1,53 +0,0 @@
<template>
<a-row class="layout-container">
<a-col :span="8">
<a-card class="general-card">
<template #title>
用户信息
</template>
<a-spin :loading="loading" style="width: 100%">
<a-form :model="formModel"
ref="formRef"
label-align="right"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 16 }"
:rules="formRules">
<!-- 用户名 -->
<a-form-item field="username" label="用户名">
<a-input v-model="formModel.username" disabled />
</a-form-item>
<!-- 花名 -->
<a-form-item field="nickname" label="花名">
<a-input v-model="formModel.nickname" placeholder="请输入花名" />
</a-form-item>
<!-- 手机号 -->
<a-form-item field="mobile" label="手机号">
<a-input v-model="formModel.mobile" placeholder="请输入手机号" />
</a-form-item>
<!-- 邮箱 -->
<a-form-item field="email" label="邮箱">
<a-input v-model="formModel.email" placeholder="请输入邮箱" />
</a-form-item>
</a-form>
</a-spin>
</a-card>
</a-col>
</a-row>
</template>
<script lang="ts" setup>
import type { UserUpdateRequest } from '@/api/user/user';
import useLoading from '@/hooks/loading';
import { ref } from 'vue';
import formRules from '../user/types/form.rules';
const { loading, setLoading } = useLoading();
const formRef = ref();
const formModel = ref<UserUpdateRequest>({});
</script>
<style lang="less" scoped>
</style>