新增:新增获取用户信息 API,未设置头像时,前端将根据用户性别显示对应默认头像

This commit is contained in:
Charles7c 2023-01-02 10:23:19 +08:00
parent 21f5aceccf
commit 88755ab720
26 changed files with 325 additions and 563 deletions

View File

@ -190,27 +190,28 @@ continew-admin # 全局通用项目配置及依赖版本管理
continew-admin
└─ continew-admin-ui # 前端项目
├─ src
│ ├─ api # 请求接口
│ │ └─ auth # 认证模块
│ ├─ assets # 静态资源
│ │ └─ style # 全局样式
│ ├─ assets # 静态资源
│ ├─ components # 通用业务组件
│ ├─ config # 全局配置(包含 echarts 主题)
│ │ └─ settings.json # 配置文件
│ ├─ directives # 指令集(如需,可自行补充)
│ ├─ hooks # 全局 hooks
│ ├─ layout # 布局
│ ├─ locale # 国际化语言包
│ ├─ mock # 模拟数据
│ ├─ router # 路由配置
│ ├─ store # 状态管理中心
│ ├─ types # Typescript 类型
│ ├─ utils # 工具库
│ ├─ views # 页面模板
│ │ └─ login # 登录模块
│ ├─ App.vue # 视图入口
│ └─ main.ts # 入口文件
│ ├─ api # 请求接口
│ │ └─ auth # 认证模块
│ ├─ assets # 静态资源
│ │ └─ style # 全局样式
│ ├─ assets # 静态资源
│ ├─ components # 通用业务组件
│ ├─ config # 全局配置(包含 echarts 主题)
│ │ └─ settings.json # 配置文件
│ ├─ directives # 指令集(如需,可自行补充)
│ ├─ hooks # 全局 hooks
│ ├─ layout # 布局
│ ├─ locale # 国际化语言包
│ ├─ mock # 模拟数据
│ ├─ router # 路由配置
│ ├─ store # 状态管理中心
│ ├─ types # Typescript 类型
│ ├─ utils # 工具库
│ ├─ views # 页面模板
│ │ ├─ login # 登录模块
│ │ └─ user # 用户模块(用户设置、用户中心)
│ ├─ App.vue # 视图入口
│ └─ main.ts # 入口文件
├─ .env.development
├─ .env.production
├─ index.html

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.auth.model.vo;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.experimental.Accessors;
import io.swagger.v3.oas.annotations.media.Schema;
import com.fasterxml.jackson.annotation.JsonIgnore;
import cn.hutool.core.util.DesensitizedUtil;
import top.charles7c.cnadmin.common.enums.GenderEnum;
/**
* 用户信息
*
* @author Charles7c
* @since 2022/12/29 20:15
*/
@Data
@Accessors(chain = true)
@Schema(description = "用户信息")
public class UserInfoVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@Schema(description = "用户ID")
private Long userId;
/**
* 用户名
*/
@Schema(description = "用户名")
private String username;
/**
* 昵称
*/
@Schema(description = "昵称")
private String nickname;
/**
* 性别0未知 1男 2女
*/
@Schema(description = "性别0未知 1男 2女")
private GenderEnum gender;
/**
* 手机号码
*/
@Schema(description = "手机号码")
private String phone;
/**
* 邮箱
*/
@Schema(description = "邮箱")
private String email;
/**
* 头像地址
*/
@Schema(description = "头像地址")
private String avatar;
/**
* 备注
*/
@Schema(description = "备注")
private String notes;
/**
* 最后一次修改密码的时间
*/
@Schema(description = "最后一次修改密码的时间")
private LocalDateTime pwdResetTime;
/**
* 创建时间
*/
@JsonIgnore
private LocalDateTime createTime;
/**
* 注册日期
*/
@Schema(description = "注册日期")
private LocalDate registrationDate;
/**
* 用户角色临时 mock 写完角色体系后移除
*/
private String role = "admin";
public String getPhone() {
return DesensitizedUtil.mobilePhone(phone);
}
public LocalDate getRegistrationDate() {
return createTime.toLocalDate();
}
}

View File

@ -28,7 +28,7 @@ export function logout() {
}
export function getUserInfo() {
return axios.get<UserState>('/api/user/info');
return axios.get<UserState>('/auth/user/info');
}
export function getMenuList() {

View File

@ -40,40 +40,9 @@ export function saveUserInfo() {
}
export interface BasicInfoModel {
email: string;
username: string;
nickname: string;
countryRegion: string;
area: string;
address: string;
profile: string;
}
export interface EnterpriseCertificationModel {
accountType: number;
status: number;
time: string;
legalPerson: string;
certificateType: string;
authenticationNumber: string;
enterpriseName: string;
enterpriseCertificateType: string;
organizationCode: string;
}
export type CertificationRecord = Array<{
certificationType: number;
certificationContent: string;
status: number;
time: string;
}>;
export interface UnitCertification {
enterpriseInfo: EnterpriseCertificationModel;
record: CertificationRecord;
}
export function queryCertification() {
return axios.get<UnitCertification>('/api/user/certification');
gender: number;
}
export function userUploadApi(

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -144,7 +144,7 @@
:size="32"
:style="{ marginRight: '8px', cursor: 'pointer' }"
>
<img alt="avatar" :src="avatar" />
<img alt="avatar" :src="loginStore.avatar ?? getAvatar(loginStore.gender)" />
</a-avatar>
<template #content>
<a-doption>
@ -194,6 +194,7 @@
import { LOCALE_OPTIONS } from '@/locale';
import useLocale from '@/hooks/locale';
import useUser from '@/hooks/user';
import getAvatar from "@/utils/avatar";
import MessageBox from '../message-box/index.vue';
const appStore = useAppStore();
@ -202,9 +203,6 @@
const { changeLocale } = useLocale();
const { isFullscreen, toggle: toggleFullScreen } = useFullscreen();
const locales = [...LOCALE_OPTIONS];
const avatar = computed(() => {
return loginStore.avatar;
});
const theme = computed(() => {
return appStore.theme;
});
@ -267,6 +265,11 @@
display: flex;
align-items: center;
padding-left: 20px;
img {
width: 32px;
height: 32px;
}
}
.right-side {

View File

@ -13,21 +13,25 @@ import useAppStore from '../app';
const useLoginStore = defineStore('user', {
state: (): UserState => ({
nickname: undefined,
avatar: undefined,
email: undefined,
userId: 1,
username: '',
nickname: '',
gender: 0,
phone: undefined,
job: undefined,
jobName: undefined,
organization: undefined,
organizationName: undefined,
location: undefined,
locationName: undefined,
introduction: undefined,
personalWebsite: undefined,
email: '',
avatar: undefined,
notes: undefined,
pwdResetTime: undefined,
registrationDate: undefined,
accountId: undefined,
certification: undefined,
job: 'backend',
jobName: '后端艺术家',
organization: 'Backend',
organizationName: '后端',
location: 'beijing',
locationName: '北京',
introduction: '低调星人',
personalWebsite: 'https://blog.charles7c.top',
role: '',
}),
@ -38,29 +42,6 @@ const useLoginStore = defineStore('user', {
},
actions: {
switchRoles() {
return new Promise((resolve) => {
this.role = this.role === 'user' ? 'admin' : 'user';
resolve(this.role);
});
},
// Set user's information
setInfo(partial: Partial<UserState>) {
this.$patch(partial);
},
// Reset user's information
resetInfo() {
this.$reset();
},
// Get user's information
async info() {
const res = await getUserInfo();
this.setInfo(res.data);
},
// 获取图片验证码
getImgCaptcha() {
return getCaptcha();
@ -92,6 +73,28 @@ const useLoginStore = defineStore('user', {
removeRouteListener();
appStore.clearServerMenu();
},
// 获取用户信息
async info() {
const res = await getUserInfo();
this.setInfo(res.data);
},
// 设置用户信息
setInfo(partial: Partial<UserState>) {
this.$patch(partial);
},
// 重置用户信息
resetInfo() {
this.$reset();
},
// 切换角色
switchRoles() {
return new Promise((resolve) => {
this.role = this.role === 'user' ? 'admin' : 'user';
resolve(this.role);
});
},
},
});

View File

@ -1,19 +1,23 @@
export type RoleType = '' | '*' | 'admin' | 'user';
export interface UserState {
nickname?: string;
avatar?: string;
email?: string;
userId: number;
username: string;
nickname: string;
gender: number;
phone?: string;
email: string;
avatar?: string;
notes?: string;
pwdResetTime?: string;
registrationDate?: string;
job?: string;
jobName?: string;
organization?: string;
organizationName?: string;
location?: string;
locationName?: string;
introduction?: string;
personalWebsite?: string;
jobName?: string;
organizationName?: string;
locationName?: string;
registrationDate?: string;
accountId?: string;
certification?: number;
role: RoleType;
}

View File

@ -0,0 +1,13 @@
import Unknown from '../assets/images/avatar/unknown.png';
import Male from '../assets/images/avatar/male.png';
import Female from '../assets/images/avatar/female.png';
export default function getAvatar(gender: number | undefined) {
if (gender === 1) {
return Male;
}
if (gender === 2) {
return Female;
}
return Unknown;
}

View File

@ -12,7 +12,7 @@
<div v-if="userInfo">
<a-space :size="12">
<a-avatar :size="24">
<img :src="userInfo.avatar" />
<img :src="userInfo.avatar ?? getAvatar(userInfo.gender)" />
</a-avatar>
<a-typography-text>
{{ userInfo.nickname }} {{ $t('monitor.studioPreview.studio') }}
@ -29,6 +29,7 @@
<script lang="ts" setup>
import { useLoginStore } from '@/store';
import getAvatar from "@/utils/avatar";
const userInfo = useLoginStore();
</script>

View File

@ -60,6 +60,11 @@
display: inline-flex;
align-items: center;
img {
width: 32px;
height: 32px;
}
&-text {
margin-right: 4px;
margin-left: 4px;

View File

@ -5,7 +5,7 @@
<template #trigger-icon>
<icon-camera />
</template>
<img :src="userInfo.avatar" />
<img :src="userInfo.avatar ?? getAvatar(userInfo.gender)" />
</a-avatar>
<a-typography-title :heading="6" style="margin: 0">
{{ userInfo.nickname }}
@ -34,6 +34,7 @@
<script lang="ts" setup>
import { useLoginStore } from '@/store';
import getAvatar from "@/utils/avatar";
const userInfo = useLoginStore();
</script>

View File

@ -7,18 +7,18 @@
:wrapper-col-props="{ span: 16 }"
>
<a-form-item
field="email"
:label="$t('userSetting.basicInfo.form.label.email')"
:label="$t('userSetting.basicInfo.form.label.username')"
:rules="[
{
required: true,
message: $t('userSetting.form.error.email.required'),
message: $t('userSetting.form.error.username.required'),
},
]"
disabled
>
<a-input
v-model="formData.email"
:placeholder="$t('userSetting.basicInfo.placeholder.email')"
v-model="formData.username"
:placeholder="$t('userSetting.basicInfo.placeholder.username')"
/>
</a-form-item>
<a-form-item
@ -37,80 +37,20 @@
/>
</a-form-item>
<a-form-item
field="countryRegion"
:label="$t('userSetting.basicInfo.form.label.countryRegion')"
field="gender"
:label="$t('userSetting.basicInfo.form.label.gender')"
:rules="[
{
required: true,
message: $t('userSetting.form.error.countryRegion.required'),
message: $t('userSetting.form.error.gender.required'),
},
]"
>
<a-select
v-model="formData.countryRegion"
:placeholder="$t('userSetting.basicInfo.placeholder.area')"
>
<a-option value="China">中国</a-option>
</a-select>
</a-form-item>
<a-form-item
field="area"
:label="$t('userSetting.basicInfo.form.label.area')"
:rules="[
{
required: true,
message: $t('userSetting.form.error.area.required'),
},
]"
>
<a-cascader
v-model="formData.area"
:placeholder="$t('userSetting.basicInfo.placeholder.area')"
:options="[
{
label: '北京',
value: 'beijing',
children: [
{
label: '北京',
value: 'beijing',
children: [
{
label: '朝阳',
value: 'chaoyang',
},
],
},
],
},
]"
allow-clear
/>
</a-form-item>
<a-form-item
field="address"
:label="$t('userSetting.basicInfo.form.label.address')"
>
<a-input
v-model="formData.address"
:placeholder="$t('userSetting.basicInfo.placeholder.address')"
/>
</a-form-item>
<a-form-item
field="profile"
:label="$t('userSetting.basicInfo.form.label.profile')"
:rules="[
{
maxLength: 200,
message: $t('userSetting.form.error.profile.maxLength'),
},
]"
row-class="keep-margin"
>
<a-textarea
v-model="formData.profile"
:placeholder="$t('userSetting.basicInfo.placeholder.profile')"
/>
<a-radio-group v-model="formData.gender">
<a-radio :value="1"></a-radio>
<a-radio :value="2"></a-radio>
<a-radio :value="0" disabled>未知</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item>
<a-space>
@ -127,18 +67,19 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useLoginStore } from '@/store';
import { FormInstance } from '@arco-design/web-vue/es/form';
import { BasicInfoModel } from '@/api/user-center';
const loginStore = useLoginStore();
const formRef = ref<FormInstance>();
const formData = ref<BasicInfoModel>({
email: '',
nickname: '',
countryRegion: '',
area: '',
address: '',
profile: '',
username: loginStore.username,
nickname: loginStore.nickname,
gender: loginStore.gender,
});
//
const validate = async () => {
const res = await formRef.value?.validate();
if (!res) {
@ -146,6 +87,8 @@
// you also can use html-type to submit
}
};
//
const reset = async () => {
await formRef.value?.resetFields();
};

View File

@ -1,77 +0,0 @@
<template>
<a-card
class="general-card"
:title="$t('userSetting.certification.title.record')"
:header-style="{ border: 'none' }"
>
<a-table v-if="renderData.length" :data="renderData">
<template #columns>
<a-table-column
:title="$t('userSetting.certification.columns.certificationType')"
>
<template #cell>
{{ $t('userSetting.certification.cell.certificationType') }}
</template>
</a-table-column>
<a-table-column
:title="$t('userSetting.certification.columns.certificationContent')"
data-index="certificationContent"
/>
<a-table-column :title="$t('userSetting.certification.columns.status')">
<template #cell="{ record }">
<p v-if="record.status === 0">
<span class="circle"></span>
<span>{{ $t('userSetting.certification.cell.auditing') }}</span>
</p>
<p v-if="record.status === 1">
<span class="circle pass"></span>
<span>{{ $t('userSetting.certification.cell.pass') }}</span>
</p>
</template>
</a-table-column>
<a-table-column
:title="$t('userSetting.certification.columns.time')"
data-index="time"
/>
<a-table-column
:title="$t('userSetting.certification.columns.operation')"
>
<template #cell="{ record }">
<a-space>
<a-button type="text">
{{ $t('userSetting.certification.button.check') }}
</a-button>
<a-button v-if="record.status === 0" type="text">
{{ $t('userSetting.certification.button.withdraw') }}
</a-button>
</a-space>
</template>
</a-table-column>
</template>
</a-table>
</a-card>
</template>
<script lang="ts" setup>
import { PropType } from 'vue';
import { CertificationRecord } from '@/api/user-center';
defineProps({
renderData: {
type: Array as PropType<CertificationRecord>,
default() {
return [];
},
},
});
</script>
<style scoped lang="less">
:deep(.arco-table-th) {
&:last-child {
.arco-table-th-item-title {
margin-left: 16px;
}
}
}
</style>

View File

@ -1,37 +0,0 @@
<template>
<a-spin :loading="loading" style="width: 100%">
<EnterpriseCertification :enterprise-info="data.enterpriseInfo" />
<CertificationRecords :render-data="data.record" />
</a-spin>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import {
queryCertification,
UnitCertification,
EnterpriseCertificationModel,
} from '@/api/user-center';
import useLoading from '@/hooks/loading';
import EnterpriseCertification from './enterprise-certification.vue';
import CertificationRecords from './certification-records.vue';
const { loading, setLoading } = useLoading(true);
const data = ref<UnitCertification>({
enterpriseInfo: {} as EnterpriseCertificationModel,
record: [],
});
const fetchData = async () => {
try {
const { data: resData } = await queryCertification();
data.value = resData;
} catch (err) {
// you can report use errorHandler or other
} finally {
setLoading(false);
}
};
fetchData();
</script>
<style scoped lang="less"></style>

View File

@ -1,116 +0,0 @@
<template>
<a-card
class="general-card"
:title="$t('userSetting.certification.title.enterprise')"
:header-style="{ padding: '0px 20px 16px 20px' }"
>
<template #extra>
<a-link>{{ $t('userSetting.certification.extra.enterprise') }}</a-link>
</template>
<a-descriptions
class="card-content"
:data="renderData"
:column="3"
align="right"
layout="inline-horizontal"
:label-style="{ fontWeight: 'normal' }"
:value-style="{
width: '200px',
paddingLeft: '8px',
textAlign: 'left',
}"
>
<template #label="{ label }">{{ $t(label) }} :</template>
<template #value="{ value, data }">
<a-tag
v-if="data.label === 'userSetting.certification.label.status'"
color="green"
size="small"
>
已认证
</a-tag>
<span v-else>{{ value }}</span>
</template>
</a-descriptions>
</a-card>
</template>
<script lang="ts" setup>
import { PropType, computed } from 'vue';
import { EnterpriseCertificationModel } from '@/api/user-center';
import type { DescData } from '@arco-design/web-vue/es/descriptions/interface';
const props = defineProps({
enterpriseInfo: {
type: Object as PropType<EnterpriseCertificationModel>,
required: true,
},
});
const renderData = computed(() => {
const {
accountType,
status,
time,
legalPerson,
certificateType,
authenticationNumber,
enterpriseName,
enterpriseCertificateType,
organizationCode,
} = props.enterpriseInfo;
return [
{
label: 'userSetting.certification.label.accountType',
value: accountType,
},
{
label: 'userSetting.certification.label.status',
value: status,
},
{
label: 'userSetting.certification.label.time',
value: time,
},
{
label: 'userSetting.certification.label.legalPerson',
value: legalPerson,
},
{
label: 'userSetting.certification.label.certificateType',
value: certificateType,
},
{
label: 'userSetting.certification.label.authenticationNumber',
value: authenticationNumber,
},
{
label: 'userSetting.certification.label.enterpriseName',
value: enterpriseName,
},
{
label: 'userSetting.certification.label.enterpriseCertificateType',
value: enterpriseCertificateType,
},
{
label: 'userSetting.certification.label.organizationCode',
value: organizationCode,
},
] as DescData[];
});
</script>
<style scoped lang="less">
.card-content {
width: 100%;
padding: 20px;
background-color: rgb(var(--gray-1));
}
.item-label {
min-width: 98px;
text-align: right;
color: var(--color-text-8);
&:after {
content: ':';
}
}
</style>

View File

@ -9,7 +9,10 @@
</template>
<template #description>
<div class="content">
<a-typography-paragraph>
<a-typography-paragraph v-if="loginStore.pwdResetTime">
已设置
</a-typography-paragraph>
<a-typography-paragraph v-else class="tip">
{{ $t('userSetting.SecuritySettings.placeholder.password') }}
</a-typography-paragraph>
</div>
@ -21,29 +24,6 @@
</template>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-list-item-meta>
<template #avatar>
<a-typography-paragraph>
{{ $t('userSetting.SecuritySettings.form.label.securityQuestion') }}
</a-typography-paragraph>
</template>
<template #description>
<div class="content">
<a-typography-paragraph class="tip">
{{
$t('userSetting.SecuritySettings.placeholder.securityQuestion')
}}
</a-typography-paragraph>
</div>
<div class="operation">
<a-link>
{{ $t('userSetting.SecuritySettings.button.settings') }}
</a-link>
</div>
</template>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-list-item-meta>
<template #avatar>
@ -53,8 +33,11 @@
</template>
<template #description>
<div class="content">
<a-typography-paragraph>
已绑定188******00
<a-typography-paragraph v-if="loginStore.phone">
已绑定{{ loginStore.phone }}
</a-typography-paragraph>
<a-typography-paragraph v-else class="tip">
{{ $t('userSetting.SecuritySettings.placeholder.phone') }}
</a-typography-paragraph>
</div>
<div class="operation">
@ -74,7 +57,10 @@
</template>
<template #description>
<div class="content">
<a-typography-paragraph class="tip">
<a-typography-paragraph v-if="loginStore.email">
已绑定{{ loginStore.email }}
</a-typography-paragraph>
<a-typography-paragraph v-else class="tip">
{{ $t('userSetting.SecuritySettings.placeholder.email') }}
</a-typography-paragraph>
</div>
@ -89,7 +75,11 @@
</a-list>
</template>
<script lang="ts" setup></script>
<script lang="ts" setup>
import { useLoginStore } from '@/store';
const loginStore = useLoginStore();
</script>
<style scoped lang="less">
:deep(.arco-list-item) {

View File

@ -36,13 +36,17 @@
>
<template #label="{ label }">{{ $t(label) }} :</template>
<template #value="{ value, data }">
<a-tag
v-if="data.label === 'userSetting.label.certification'"
color="green"
size="small"
>
已认证
</a-tag>
<div v-if="data.label === 'userSetting.label.gender'">
<div v-if="loginStore.gender === 1">
<icon-man style="color: #19BBF1" />
</div>
<div v-else-if="loginStore.gender === 2">
<icon-woman style="color: #FA7FA9" />
</div>
<div v-else>未知</div>
</div>
<span v-else>{{ value }}</span>
</template>
</a-descriptions>
@ -59,12 +63,13 @@
import { useLoginStore } from '@/store';
import { userUploadApi } from '@/api/user-center';
import type { DescData } from '@arco-design/web-vue/es/descriptions/interface';
import getAvatar from "@/utils/avatar";
const loginStore = useLoginStore();
const file = {
uid: '-2',
name: 'avatar.png',
url: loginStore.avatar,
url: loginStore.avatar ?? getAvatar(loginStore.gender),
};
const renderData = [
{
@ -72,17 +77,17 @@
value: loginStore.nickname,
},
{
label: 'userSetting.label.certification',
value: loginStore.certification,
},
{
label: 'userSetting.label.accountId',
value: loginStore.accountId,
label: 'userSetting.label.gender',
value: loginStore.gender,
},
{
label: 'userSetting.label.phone',
value: loginStore.phone,
},
{
label: 'userSetting.label.email',
value: loginStore.email,
},
{
label: 'userSetting.label.registrationDate',
value: loginStore.registrationDate,

View File

@ -15,9 +15,6 @@
<a-tab-pane key="2" :title="$t('userSetting.tab.securitySettings')">
<SecuritySettings />
</a-tab-pane>
<a-tab-pane key="3" :title="$t('userSetting.tab.certification')">
<Certification />
</a-tab-pane>
</a-tabs>
</a-col>
</a-row>
@ -28,7 +25,6 @@
import UserPanel from './components/user-panel.vue';
import BasicInformation from './components/basic-information.vue';
import SecuritySettings from './components/security-settings.vue';
import Certification from './components/certification.vue';
</script>
<script lang="ts">

View File

@ -18,74 +18,30 @@ export default {
'userSetting.cancel': 'Cancel',
'userSetting.reset': 'Reset',
// new
'userSetting.label.certification': 'Certification',
'userSetting.label.phone': 'Phone',
'userSetting.label.accountId': 'Account Id',
'userSetting.label.email': 'Email',
'userSetting.label.gender': 'Gender',
'userSetting.label.registrationDate': 'Registration Date',
'userSetting.tab.basicInformation': 'Basic Information',
'userSetting.tab.securitySettings': 'Security Settings',
'userSetting.tab.certification': 'Certification',
'userSetting.basicInfo.form.label.email': 'Email',
'userSetting.basicInfo.placeholder.email': `Please enter your email address, such as xxx{'@'}126.com`,
'userSetting.form.error.email.required': 'Please enter email address',
'userSetting.basicInfo.form.label.username': 'Username',
'userSetting.basicInfo.placeholder.username': 'Please enter username',
'userSetting.form.error.username.required': 'Please enter username',
'userSetting.basicInfo.form.label.nickname': 'Nickname',
'userSetting.basicInfo.placeholder.nickname': 'Please enter nickname',
'userSetting.form.error.nickname.required': 'Please enter nickname',
'userSetting.basicInfo.form.label.countryRegion': 'Country/region',
'userSetting.basicInfo.placeholder.countryRegion':
'Please select country/region',
'userSetting.form.error.countryRegion.required':
'Please select country/region',
'userSetting.basicInfo.form.label.area': 'Area',
'userSetting.basicInfo.placeholder.area': 'Please select area',
'userSetting.form.error.area.required': 'Please Select a area',
'userSetting.basicInfo.form.label.address': 'Address',
'userSetting.basicInfo.placeholder.address': 'Please enter address',
'userSetting.basicInfo.form.label.profile': 'Personal profile',
'userSetting.basicInfo.placeholder.profile':
'Please enter your profile, no more than 200 words',
'userSetting.form.error.profile.maxLength': 'No more than 200 words',
'userSetting.basicInfo.form.label.gender': 'Gender',
'userSetting.form.error.gender.required': 'Please select gender',
'userSetting.SecuritySettings.form.label.password': 'Login Password',
'userSetting.SecuritySettings.placeholder.password':
'Has been set. The password must contain at least six letters, digits, and special characters except Spaces. The password must contain both uppercase and lowercase letters.',
'userSetting.SecuritySettings.form.label.securityQuestion':
'Security Question',
'userSetting.SecuritySettings.placeholder.securityQuestion':
'You have not set the password protection question. The password protection question can effectively protect the account security.',
'You have not set a password yet. The password must contain at least six letters, digits, and special characters except Spaces.',
'userSetting.SecuritySettings.form.label.phone': 'Phone',
// 'userSetting.SecuritySettings.placeholder.phone': '已绑定188******00',
'userSetting.SecuritySettings.placeholder.phone':
'You have not set a phone yet. The phone binding can be used to retrieve passwords and receive notifications and SMS login.',
'userSetting.SecuritySettings.form.label.email': 'Email',
'userSetting.SecuritySettings.placeholder.email':
'You have not set a mailbox yet. The mailbox binding can be used to retrieve passwords and receive notifications.',
'userSetting.SecuritySettings.button.settings': 'Settings',
'userSetting.SecuritySettings.button.update': 'Update',
'userSetting.certification.title.enterprise':
'Enterprise Real Name Authentication',
'userSetting.certification.extra.enterprise':
'Modifying an Authentication Body',
'userSetting.certification.label.accountType': 'Account Type',
'userSetting.certification.label.status': 'status',
'userSetting.certification.label.time': 'time',
'userSetting.certification.label.legalPerson': 'Legal Person Name',
'userSetting.certification.label.certificateType':
'Types of legal person documents',
'userSetting.certification.label.authenticationNumber':
'Legal person certification number',
'userSetting.certification.label.enterpriseName': 'Enterprise Name',
'userSetting.certification.label.enterpriseCertificateType':
'Types of corporate certificates',
'userSetting.certification.label.organizationCode': 'Organization Code',
'userSetting.certification.title.record': 'Certification Records',
'userSetting.certification.columns.certificationType': 'Certification Type',
'userSetting.certification.cell.certificationType':
'Enterprise certificate Certification',
'userSetting.certification.columns.certificationContent':
'Certification Content',
'userSetting.certification.columns.status': 'Status',
'userSetting.certification.cell.pass': 'Pass',
'userSetting.certification.cell.auditing': 'Auditing',
'userSetting.certification.columns.time': 'Time',
'userSetting.certification.columns.operation': 'Operation',
'userSetting.certification.button.check': 'Check',
'userSetting.certification.button.withdraw': 'Withdraw',
};

View File

@ -18,64 +18,30 @@ export default {
'userSetting.cancel': '取消',
'userSetting.reset': '重置',
// new
'userSetting.label.certification': '实名认证',
'userSetting.label.phone': '手机号码',
'userSetting.label.accountId': '账号ID',
'userSetting.label.registrationDate': '注册时间',
'userSetting.label.email': '邮箱',
'userSetting.label.gender': '性别',
'userSetting.label.registrationDate': '注册日期',
'userSetting.tab.basicInformation': '基础信息',
'userSetting.tab.securitySettings': '安全设置',
'userSetting.tab.certification': '实名认证',
'userSetting.basicInfo.form.label.email': '邮箱',
'userSetting.basicInfo.placeholder.email': `请输入邮箱地址如xxx{'@'}126.com`,
'userSetting.form.error.email.required': '请输入邮箱',
'userSetting.basicInfo.form.label.username': '用户名',
'userSetting.basicInfo.placeholder.username': '请输入您的用户名',
'userSetting.form.error.username.required': '请输入用户名',
'userSetting.basicInfo.form.label.nickname': '昵称',
'userSetting.basicInfo.placeholder.nickname': '请输入您的昵称',
'userSetting.form.error.nickname.required': '请输入昵称',
'userSetting.basicInfo.form.label.countryRegion': '国家/地区',
'userSetting.basicInfo.placeholder.countryRegion': '请选择',
'userSetting.form.error.countryRegion.required': '请选择国家/地区',
'userSetting.basicInfo.form.label.area': '所在区域',
'userSetting.basicInfo.placeholder.area': '请选择',
'userSetting.form.error.area.required': '请选择所在区域',
'userSetting.basicInfo.form.label.address': '具体地址',
'userSetting.basicInfo.placeholder.address': '请输入您的地址',
'userSetting.basicInfo.form.label.profile': '个人简介',
'userSetting.basicInfo.placeholder.profile':
'请输入您的个人简介最多不超过200字。',
'userSetting.form.error.profile.maxLength': '最多不超过200字',
'userSetting.basicInfo.form.label.gender': '性别',
'userSetting.form.error.gender.required': '请选择性别',
'userSetting.SecuritySettings.form.label.password': '登录密码',
'userSetting.SecuritySettings.placeholder.password':
'已设置。密码至少6位字符支持数字、字母和除空格外的特殊字符且必须同时包含数字和大小写字母。',
'userSetting.SecuritySettings.form.label.securityQuestion': '密保问题',
'userSetting.SecuritySettings.placeholder.securityQuestion':
'您暂未设置密保问题,密保问题可以有效的保护账号的安全。',
'您暂未设置密码密码至少6位字符支持数字、字母和除空格外的特殊字符。',
'userSetting.SecuritySettings.form.label.phone': '安全手机',
// 'userSetting.SecuritySettings.placeholder.phone': '已绑定188******00',
'userSetting.SecuritySettings.placeholder.phone':
'您暂未设置手机号,绑定手机号可以用来找回密码、接收通知、短信登录等。',
'userSetting.SecuritySettings.form.label.email': '安全邮箱',
'userSetting.SecuritySettings.placeholder.email':
'您暂未设置邮箱,绑定邮箱可以用来找回密码、接收通知等。',
'userSetting.SecuritySettings.button.settings': '设置',
'userSetting.SecuritySettings.button.update': '修改',
'userSetting.certification.title.enterprise': '企业实名认证',
'userSetting.certification.extra.enterprise': '修改认证主体',
'userSetting.certification.label.accountType': '账号类型',
'userSetting.certification.label.status': '认证状态',
'userSetting.certification.label.time': '认证时间',
'userSetting.certification.label.legalPerson': '法人姓名',
'userSetting.certification.label.certificateType': '法人证件类型',
'userSetting.certification.label.authenticationNumber': '法人认证号码',
'userSetting.certification.label.enterpriseName': '企业名称',
'userSetting.certification.label.enterpriseCertificateType': '企业证件类型',
'userSetting.certification.label.organizationCode': '组织机构代码',
'userSetting.certification.title.record': '认证记录',
'userSetting.certification.columns.certificationType': '认证类型',
'userSetting.certification.cell.certificationType': '企业证件认证',
'userSetting.certification.columns.certificationContent': '认证内容',
'userSetting.certification.columns.status': '当前状态',
'userSetting.certification.cell.pass': '已通过',
'userSetting.certification.cell.auditing': '审核中',
'userSetting.certification.columns.time': '创建时间',
'userSetting.certification.columns.operation': '操作',
'userSetting.certification.button.check': '查看',
'userSetting.certification.button.withdraw': '撤回',
};

View File

@ -29,17 +29,21 @@ import org.springframework.web.bind.annotation.*;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import top.charles7c.cnadmin.auth.config.properties.CaptchaProperties;
import top.charles7c.cnadmin.auth.model.request.LoginRequest;
import top.charles7c.cnadmin.auth.model.vo.LoginVO;
import top.charles7c.cnadmin.auth.model.vo.UserInfoVO;
import top.charles7c.cnadmin.auth.service.LoginService;
import top.charles7c.cnadmin.common.config.properties.RsaProperties;
import top.charles7c.cnadmin.common.model.dto.LoginUser;
import top.charles7c.cnadmin.common.model.vo.R;
import top.charles7c.cnadmin.common.util.CheckUtils;
import top.charles7c.cnadmin.common.util.ExceptionUtils;
import top.charles7c.cnadmin.common.util.RedisUtils;
import top.charles7c.cnadmin.common.util.SecureUtils;
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
/**
* 登录 API
@ -85,4 +89,12 @@ public class LoginController {
StpUtil.logout();
return R.ok();
}
@Operation(summary = "获取用户信息", description = "获取登录用户信息")
@GetMapping("/user/info")
public R<UserInfoVO> getUserInfo() {
LoginUser loginUser = LoginHelper.getLoginUser();
UserInfoVO userInfoVo = BeanUtil.copyProperties(loginUser, UserInfoVO.class);
return R.ok(userInfoVo);
}
}

View File

@ -2,5 +2,5 @@
-- changeset Charles7c:1
-- 初始化默认用户admin/123456test/123456
INSERT IGNORE INTO `sys_user` VALUES (1, 'admin', '超级管理员', 'f0df7414507bcb57e07e18555821228a', 1, NULL, 'charles7c@126.com', NULL, NULL, 1, NULL, 1, NOW(), 1, NOW());
INSERT IGNORE INTO `sys_user` VALUES (2, 'test', '测试员', '8e114197e1b33783a00542ad67e80516', 0, NULL, NULL, NULL, NULL, 2, NULL, 1, NOW(), 1, NOW());
INSERT IGNORE INTO `sys_user` VALUES (1, 'admin', '超级管理员', 'f0df7414507bcb57e07e18555821228a', 1, '18888888888', 'charles7c@126.com', NULL, NULL, 1, NOW(), 1, NOW(), 1, NOW());
INSERT IGNORE INTO `sys_user` VALUES (2, 'test', '测试员', '8e114197e1b33783a00542ad67e80516', 0, NULL, NULL, NULL, NULL, 2, NOW(), 1, NOW(), 1, NOW());

View File

@ -24,7 +24,6 @@ CREATE TABLE IF NOT EXISTS `sys_user` (
INDEX `idx_updateUser`(`update_user`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- changeset Charles7c:2
CREATE TABLE IF NOT EXISTS `sys_log` (
`log_id` bigint(20) unsigned AUTO_INCREMENT COMMENT '日志ID',
`log_level` varchar(255) DEFAULT NULL COMMENT '日志级别',