新增:新增上传头像 API,采用本地存储方式存储头像

This commit is contained in:
2023-01-05 22:32:23 +08:00
parent e77c77419b
commit 5252c54c48
54 changed files with 931 additions and 937 deletions

View File

@@ -0,0 +1,96 @@
<template>
<a-form
ref="formRef"
:model="formData"
class="form"
:label-col-props="{ span: 8 }"
:wrapper-col-props="{ span: 16 }"
>
<a-form-item
:label="$t('userCenter.basicInfo.form.label.username')"
:rules="[
{
required: true,
message: $t('userCenter.form.error.username.required'),
},
]"
disabled
>
<a-input
v-model="formData.username"
:placeholder="$t('userCenter.basicInfo.placeholder.username')"
/>
</a-form-item>
<a-form-item
field="nickname"
:label="$t('userCenter.basicInfo.form.label.nickname')"
:rules="[
{
required: true,
message: $t('userCenter.form.error.nickname.required'),
},
]"
>
<a-input
v-model="formData.nickname"
:placeholder="$t('userCenter.basicInfo.placeholder.nickname')"
/>
</a-form-item>
<a-form-item
field="gender"
:label="$t('userCenter.basicInfo.form.label.gender')"
>
<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>
<a-button type="primary" @click="validate">
{{ $t('userCenter.save') }}
</a-button>
<a-button type="secondary" @click="reset">
{{ $t('userCenter.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-form>
</template>
<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/system/user-center';
const loginStore = useLoginStore();
const formRef = ref<FormInstance>();
const formData = ref<BasicInfoModel>({
username: loginStore.username,
nickname: loginStore.nickname,
gender: loginStore.gender,
});
// 保存
const validate = async () => {
const res = await formRef.value?.validate();
if (!res) {
// do some thing
// you also can use html-type to submit
}
};
// 重置
const reset = async () => {
await formRef.value?.resetFields();
};
</script>
<style scoped lang="less">
.form {
width: 540px;
margin: 0 auto;
}
</style>

View File

@@ -0,0 +1,114 @@
<template>
<a-list :bordered="false">
<a-list-item>
<a-list-item-meta>
<template #avatar>
<a-typography-paragraph>
{{ $t('userCenter.SecuritySettings.form.label.password') }}
</a-typography-paragraph>
</template>
<template #description>
<div class="content">
<a-typography-paragraph v-if="loginStore.pwdResetTime">
已设置
</a-typography-paragraph>
<a-typography-paragraph v-else class="tip">
{{ $t('userCenter.SecuritySettings.placeholder.password') }}
</a-typography-paragraph>
</div>
<div class="operation">
<a-link>
{{ $t('userCenter.SecuritySettings.button.update') }}
</a-link>
</div>
</template>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-list-item-meta>
<template #avatar>
<a-typography-paragraph>
{{ $t('userCenter.SecuritySettings.form.label.phone') }}
</a-typography-paragraph>
</template>
<template #description>
<div class="content">
<a-typography-paragraph v-if="loginStore.phone">
已绑定{{ loginStore.phone }}
</a-typography-paragraph>
<a-typography-paragraph v-else class="tip">
{{ $t('userCenter.SecuritySettings.placeholder.phone') }}
</a-typography-paragraph>
</div>
<div class="operation">
<a-link>
{{ $t('userCenter.SecuritySettings.button.update') }}
</a-link>
</div>
</template>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-list-item-meta>
<template #avatar>
<a-typography-paragraph>
{{ $t('userCenter.SecuritySettings.form.label.email') }}
</a-typography-paragraph>
</template>
<template #description>
<div class="content">
<a-typography-paragraph v-if="loginStore.email">
已绑定{{ loginStore.email }}
</a-typography-paragraph>
<a-typography-paragraph v-else class="tip">
{{ $t('userCenter.SecuritySettings.placeholder.email') }}
</a-typography-paragraph>
</div>
<div class="operation">
<a-link>
{{ $t('userCenter.SecuritySettings.button.update') }}
</a-link>
</div>
</template>
</a-list-item-meta>
</a-list-item>
</a-list>
</template>
<script lang="ts" setup>
import { useLoginStore } from '@/store';
const loginStore = useLoginStore();
</script>
<style scoped lang="less">
:deep(.arco-list-item) {
border-bottom: none !important;
.arco-typography {
margin-bottom: 20px;
}
.arco-list-item-meta-avatar {
margin-bottom: 1px;
}
.arco-list-item-meta {
padding: 0;
}
}
:deep(.arco-list-item-meta-content) {
flex: 1;
border-bottom: 1px solid var(--color-neutral-3);
.arco-list-item-meta-description {
display: flex;
flex-flow: row;
justify-content: space-between;
.tip {
color: rgb(var(--gray-6));
}
.operation {
margin-right: 6px;
}
}
}
</style>

View File

@@ -0,0 +1,155 @@
<template>
<a-card :bordered="false">
<a-space :size="54">
<a-upload
:custom-request="handleUpload"
list-type="picture-card"
:file-list="avatarList"
:show-upload-button="true"
:show-file-list="false"
@change="changeAvatar"
>
<template #upload-button>
<a-avatar :size="100" class="info-avatar">
<template #trigger-icon>
<icon-camera />
</template>
<img v-if="avatarList.length" :src="avatarList[0].url" />
</a-avatar>
</template>
</a-upload>
<a-descriptions
:data="renderData"
:column="2"
align="right"
layout="inline-horizontal"
:label-style="{
width: '140px',
fontWeight: 'normal',
color: 'rgb(var(--gray-8))',
}"
:value-style="{
width: '200px',
paddingLeft: '8px',
textAlign: 'left',
}"
>
<template #label="{ label }">{{ $t(label) }} :</template>
<template #value="{ value, data }">
<div v-if="data.label === 'userCenter.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>
</a-space>
</a-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import type {
FileItem,
RequestOption,
} from '@arco-design/web-vue/es/upload/interfaces';
import { useLoginStore } from '@/store';
import { uploadAvatar } from '@/api/system/user-center';
import type { DescData } from '@arco-design/web-vue/es/descriptions/interface';
import getAvatar from "@/utils/avatar";
import { Message } from "@arco-design/web-vue";
const loginStore = useLoginStore();
const avatar = {
uid: '-2',
name: 'avatar.png',
url: getAvatar(loginStore),
};
const renderData = [
{
label: 'userCenter.label.nickname',
value: loginStore.nickname,
},
{
label: 'userCenter.label.gender',
value: loginStore.gender,
},
{
label: 'userCenter.label.phone',
value: loginStore.phone,
},
{
label: 'userCenter.label.email',
value: loginStore.email,
},
{
label: 'userCenter.label.registrationDate',
value: loginStore.registrationDate,
},
] as DescData[];
const avatarList = ref<FileItem[]>([avatar]);
// 切换头像
const changeAvatar = (fileItemList: FileItem[], currentFile: FileItem) => {
avatarList.value = [currentFile];
};
// 上传头像
const handleUpload = (options: RequestOption) => {
const controller = new AbortController();
(async function requestWrap() {
const {
onProgress,
onError,
onSuccess,
fileItem,
name = 'avatarFile',
} = options;
onProgress(20);
const formData = new FormData();
formData.append(name as string, fileItem.file as Blob);
try {
const res = await uploadAvatar(formData);
onSuccess(res);
Message.success({
content: res.msg || '网络错误',
duration: 3 * 1000,
});
// 更换头像
loginStore.avatar = res.data.avatar;
} catch (error) {
onError(error);
}
})();
return {
abort() {
controller.abort();
},
};
};
</script>
<style scoped lang="less">
.arco-card {
padding: 14px 0 4px 4px;
border-radius: 4px;
}
:deep(.arco-avatar-trigger-icon-button) {
width: 32px;
height: 32px;
line-height: 32px;
background-color: #e8f3ff;
.arco-icon-camera {
margin-top: 8px;
color: rgb(var(--arcoblue-6));
font-size: 14px;
}
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<div class="container">
<Breadcrumb :items="['menu.user.center']" />
<a-row style="margin-bottom: 16px">
<a-col :span="24">
<UserPanel />
</a-col>
</a-row>
<a-row class="wrapper">
<a-col :span="24">
<a-tabs default-active-key="1" type="rounded">
<a-tab-pane key="1" :title="$t('userCenter.tab.basicInformation')">
<BasicInformation />
</a-tab-pane>
<a-tab-pane key="2" :title="$t('userCenter.tab.securitySettings')">
<SecuritySettings />
</a-tab-pane>
</a-tabs>
</a-col>
</a-row>
</div>
</template>
<script lang="ts" setup>
import UserPanel from './components/user-panel.vue';
import BasicInformation from './components/basic-information.vue';
import SecuritySettings from './components/security-settings.vue';
</script>
<script lang="ts">
export default {
name: 'UserCenter',
};
</script>
<style scoped lang="less">
.container {
padding: 0 20px 20px 20px;
}
.wrapper {
padding: 20px 0 0 20px;
min-height: 580px;
background-color: var(--color-bg-2);
border-radius: 4px;
}
:deep(.section-title) {
margin-top: 0;
margin-bottom: 16px;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,31 @@
export default {
'menu.user.center': 'User Center',
'userCenter.label.nickname': 'Nick Name',
'userCenter.label.gender': 'Gender',
'userCenter.label.phone': 'Phone',
'userCenter.label.email': 'Email',
'userCenter.label.registrationDate': 'Registration Date',
'userCenter.tab.basicInformation': 'Basic Information',
'userCenter.basicInfo.form.label.username': 'Username',
'userCenter.basicInfo.placeholder.username': 'Please enter username',
'userCenter.form.error.username.required': 'Please enter username',
'userCenter.basicInfo.form.label.nickname': 'Nickname',
'userCenter.basicInfo.placeholder.nickname': 'Please enter nickname',
'userCenter.form.error.nickname.required': 'Please enter nickname',
'userCenter.basicInfo.form.label.gender': 'Gender',
'userCenter.save': 'Save',
'userCenter.reset': 'Reset',
'userCenter.tab.securitySettings': 'Security Settings',
'userCenter.SecuritySettings.form.label.password': 'Login Password',
'userCenter.SecuritySettings.placeholder.password':
'You have not set a password yet. The password must contain at least six letters, digits, and special characters except Spaces.',
'userCenter.SecuritySettings.form.label.phone': 'Phone',
'userCenter.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.',
'userCenter.SecuritySettings.form.label.email': 'Email',
'userCenter.SecuritySettings.placeholder.email':
'You have not set a mailbox yet. The mailbox binding can be used to retrieve passwords and receive notifications.',
'userCenter.SecuritySettings.button.update': 'Update',
};

View File

@@ -0,0 +1,31 @@
export default {
'menu.user.center': '个人中心',
'userCenter.label.nickname': '昵称',
'userCenter.label.gender': '性别',
'userCenter.label.phone': '手机号码',
'userCenter.label.email': '邮箱',
'userCenter.label.registrationDate': '注册日期',
'userCenter.tab.basicInformation': '基础信息',
'userCenter.basicInfo.form.label.username': '用户名',
'userCenter.basicInfo.placeholder.username': '请输入您的用户名',
'userCenter.form.error.username.required': '请输入用户名',
'userCenter.basicInfo.form.label.nickname': '昵称',
'userCenter.basicInfo.placeholder.nickname': '请输入您的昵称',
'userCenter.form.error.nickname.required': '请输入昵称',
'userCenter.basicInfo.form.label.gender': '性别',
'userCenter.save': '保存',
'userCenter.reset': '重置',
'userCenter.tab.securitySettings': '安全设置',
'userCenter.SecuritySettings.form.label.password': '登录密码',
'userCenter.SecuritySettings.placeholder.password':
'您暂未设置密码密码至少6位字符支持数字、字母和除空格外的特殊字符。',
'userCenter.SecuritySettings.form.label.phone': '安全手机',
'userCenter.SecuritySettings.placeholder.phone':
'您暂未设置手机号,绑定手机号可以用来找回密码、接收通知、短信登录等。',
'userCenter.SecuritySettings.form.label.email': '安全邮箱',
'userCenter.SecuritySettings.placeholder.email':
'您暂未设置邮箱,绑定邮箱可以用来找回密码、接收通知等。',
'userCenter.SecuritySettings.button.update': '修改',
};

View File

@@ -0,0 +1,42 @@
import Mock from 'mockjs';
import setupMock, { successResponseWrap } from '@/utils/setup-mock';
setupMock({
setup() {
Mock.mock(new RegExp('/api/user/save-info'), () => {
return successResponseWrap('ok');
});
Mock.mock(new RegExp('/api/user/certification'), () => {
return successResponseWrap({
enterpriseInfo: {
accountType: '企业账号',
status: 0,
time: '2022-12-27 20:00:00',
legalPerson: '张**',
certificateType: '中国身份证',
authenticationNumber: '110************123',
enterpriseName: '低调有实力的企业',
enterpriseCertificateType: '企业营业执照',
organizationCode: '7*******9',
},
record: [
{
certificationType: 1,
certificationContent: '企业实名认证,法人姓名:张**',
status: 0,
time: '2022-12-27 20:00:00',
},
{
certificationType: 1,
certificationContent: '企业实名认证,法人姓名:张**',
status: 1,
time: '2022-12-27 20:00:00',
},
],
});
});
Mock.mock(new RegExp('/api/user/upload'), () => {
return successResponseWrap('ok');
});
},
});