优化:优化表单校验相关代码,拆分安全设置组件,完善登录页面 slogan 和插图

This commit is contained in:
Charles7c 2023-01-11 21:24:57 +08:00
parent a08fd7773e
commit 73fadb8315
24 changed files with 465 additions and 338 deletions

View File

@ -216,7 +216,8 @@ continew-admin
│ ├─ views # 页面模板
│ │ ├─ login # 登录模块
│ │ └─ system # 系统管理模块
│ │ └─ user # 用户模块(用户中心)
│ │ └─ user # 用户模块
│ │ └─ center # 个人中心
│ ├─ App.vue # 视图入口
│ └─ main.ts # 入口文件
├─ .env.development

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -10,7 +10,7 @@
:style="{ margin: 0, fontSize: '18px' }"
:heading="5"
>
ContiNew Admin
{{ $t('title') }}
</a-typography-title>
<icon-menu-fold
v-if="!topMenu && appStore.device === 'mobile'"

View File

@ -1,15 +1,17 @@
import { useRouter } from 'vue-router';
import { useI18n } from "vue-i18n";
import { Message } from '@arco-design/web-vue';
import { useLoginStore } from '@/store';
export default function useUser() {
const { t } = useI18n();
const router = useRouter();
const loginStore = useLoginStore();
const logout = async (logoutTo?: string) => {
await loginStore.logout();
const currentRoute = router.currentRoute.value;
Message.success('退出成功');
Message.success(t('login.form.logout.success'));
router.push({
name: logoutTo && typeof logoutTo === 'string' ? logoutTo : 'login',
query: {

View File

@ -28,6 +28,7 @@ import localeUserCenter from '@/views/system/user/center/locale/en-US';
import localeSettings from './en-US/settings';
export default {
'title': 'ContiNew Admin',
'menu.dashboard': 'Dashboard',
'menu.server.dashboard': 'Dashboard-Server',
'menu.server.workplace': 'Workplace-Server',

View File

@ -28,6 +28,7 @@ import localeUserCenter from '@/views/system/user/center/locale/zh-CN';
import localeSettings from './zh-CN/settings';
export default {
'title': 'ContiNew Admin',
'menu.dashboard': '仪表盘',
'menu.server.dashboard': '仪表盘-服务端',
'menu.server.workplace': '工作台-服务端',

View File

@ -17,24 +17,26 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import bannerImage from '@/assets/images/login-banner.png';
import bannerImage1 from '@/assets/images/login/banner1.png';
import bannerImage2 from '@/assets/images/login/banner2.png';
import bannerImage3 from '@/assets/images/login/banner3.png';
const { t } = useI18n();
const carouselItem = computed(() => [
{
slogan: t('login.banner.slogan1'),
subSlogan: t('login.banner.subSlogan1'),
image: bannerImage,
image: bannerImage1,
},
{
slogan: t('login.banner.slogan2'),
subSlogan: t('login.banner.subSlogan2'),
image: bannerImage,
image: bannerImage2,
},
{
slogan: t('login.banner.slogan3'),
subSlogan: t('login.banner.subSlogan3'),
image: bannerImage,
image: bannerImage3,
},
]);
</script>
@ -71,13 +73,14 @@
&-sub-title {
margin-top: 8px;
margin-left: 30px;
color: var(--color-text-3);
font-size: 14px;
line-height: 22px;
}
&-image {
width: 320px;
width: 360px;
margin-top: 30px;
}
}

View File

@ -4,21 +4,21 @@
<div class="login-form-sub-title">{{ $t('login.form.subTitle') }}</div>
<div class="login-form-error-msg">{{ errorMessage }}</div>
<a-form
ref="loginFormRef"
:model="loginForm"
ref="formRef"
:model="formData"
:rules="rules"
class="login-form"
layout="vertical"
@submit="handleLogin"
>
<a-form-item
field="username"
:rules="[{ required: true, message: $t('login.form.username.errMsg') }]"
:validate-trigger="['change', 'blur']"
hide-label
>
<a-input
v-model="loginForm.username"
:placeholder="$t('login.form.username.placeholder')"
v-model="formData.username"
:placeholder="$t('login.form.placeholder.username')"
size="large"
max-length="50"
>
@ -29,13 +29,12 @@
</a-form-item>
<a-form-item
field="password"
:rules="[{ required: true, message: $t('login.form.password.errMsg') }]"
:validate-trigger="['change', 'blur']"
hide-label
>
<a-input-password
v-model="loginForm.password"
:placeholder="$t('login.form.password.placeholder')"
v-model="formData.password"
:placeholder="$t('login.form.placeholder.password')"
size="large"
allow-clear
max-length="50"
@ -48,13 +47,12 @@
<a-form-item
class="login-form-captcha"
field="captcha"
:rules="[{ required: true, message: $t('login.form.captcha.errMsg') }]"
:validate-trigger="['change', 'blur']"
hide-label
>
<a-input
v-model="loginForm.captcha"
:placeholder="$t('login.form.captcha.placeholder')"
v-model="formData.captcha"
:placeholder="$t('login.form.placeholder.captcha')"
size="large"
style="width: 63%"
allow-clear
@ -63,7 +61,7 @@
<icon-check-circle />
</template>
</a-input>
<img :src="captchaImgBase64" @click="getCaptcha" :alt="$t('login.form.captcha.placeholder')">
<img :src="captchaImgBase64" @click="getCaptcha" :alt="$t('login.form.captcha')">
</a-form-item>
<a-space :size="16" direction="vertical">
<div class="login-form-remember-me">
@ -84,9 +82,9 @@
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue';
import { ref, reactive, computed, onMounted } from "vue";
import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import { FieldRule, Message } from "@arco-design/web-vue";
import { ValidatedError } from '@arco-design/web-vue/es/form/interface';
import { useI18n } from 'vue-i18n';
// import debug from '@/utils/env';
@ -95,13 +93,12 @@
import { useLoginStore } from '@/store';
import useLoading from '@/hooks/loading';
const router = useRouter();
const { t } = useI18n();
const errorMessage = ref('');
const captchaImgBase64 = ref('');
const router = useRouter();
const { loading, setLoading } = useLoading();
const loginStore = useLoginStore();
const errorMessage = ref('');
const captchaImgBase64 = ref('');
const loginConfig = useStorage('login-config', {
rememberMe: true,
username: 'admin', //
@ -109,18 +106,30 @@
// username: !debug ? '' : 'admin', //
// password: !debug ? '' : 'admin123', //
});
const loginForm = reactive({
const formData = reactive({
username: loginConfig.value.username,
password: loginConfig.value.password,
captcha: '',
uuid: '',
});
const rules = computed((): Record<string, FieldRule[]> => {
return {
username: [
{ required: true, message: t('login.form.error.required.username') }
],
password: [
{ required: true, message: t('login.form.error.required.password') }
],
captcha: [
{ required: true, message: t('login.form.error.required.captcha') }
],
}
});
//
const getCaptcha = async () => {
const { data } = await loginStore.getImgCaptcha()
loginForm.uuid = data.uuid
formData.uuid = data.uuid
captchaImgBase64.value = data.img
}
onMounted(() => {
@ -162,7 +171,6 @@
const { username } = values;
loginConfig.value.username = rememberMe ? username : '';
} catch (err) {
// errorMessage.value = (err as Error).message;
await getCaptcha();
} finally {
setLoading(false);

View File

@ -5,7 +5,7 @@
alt="logo"
src="/logo.svg"
/>
<div class="logo-text">ContiNew Admin</div>
<div class="logo-text">{{ $t('title') }}</div>
</div>
<LoginBanner />
<div class="content">
@ -70,7 +70,6 @@
</style>
<style lang="less" scoped>
// responsive
@media (max-width: @screen-lg) {
.container {
.banner {

View File

@ -1,25 +1,31 @@
export default {
'login.form.title': 'Login to ContiNew Admin',
'login.form.subTitle': 'Continue to build the latest popular technology stack in the background management framework',
'login.form.username.placeholder': 'Please enter username',
'login.form.username.errMsg': 'Please enter username',
'login.form.password.placeholder': 'Please enter password',
'login.form.password.errMsg': 'Please enter password',
'login.form.captcha.placeholder': 'Please enter captcha',
'login.form.captcha.errMsg': 'Please enter captcha',
'login.form.placeholder.username': 'Please enter username',
'login.form.placeholder.password': 'Please enter password',
'login.form.placeholder.captcha': 'Please enter captcha',
'login.form.error.required.username': 'Please enter username',
'login.form.error.required.password': 'Please enter password',
'login.form.error.required.captcha': 'Please enter captcha',
'login.form.captcha': 'Captcha',
'login.form.rememberMe': 'Remember me',
'login.form.login': 'Login',
'login.form.register': 'Register account',
'login.form.forgetPassword': 'Forgot password',
'login.form.login.success': 'Welcome to use',
'login.form.login.errMsg': 'Login error, refresh and try again',
'login.banner.slogan1': 'Out-of-the-box high-quality template',
'login.form.login.success': 'Welcome to use',
'login.form.login.error': 'Login error, refresh and try again',
'login.form.logout.success': 'Logout success',
'login.banner.slogan1': 'Middle and background management framework',
'login.banner.subSlogan1':
'Rich page templates, covering most typical business scenarios',
'Continue New Admin, continue to build on the latest popular technology stack',
'login.banner.slogan2': 'Built-in solutions to common problems',
'login.banner.subSlogan2':
'Internationalization, routing configuration, state management everything',
'login.banner.slogan3': 'Access visualization enhancement tool AUX',
'login.banner.subSlogan3': 'Realize flexible block development',
'Rich basic functions, low threshold to use, enterprise rapid development scaffolding',
'login.banner.slogan3': 'The code is standard and open source and free',
'login.banner.subSlogan3': 'The backend code is fully compliant with the Alibaba coding specification, and the frontend code is strictly ESLint checked',
};

View File

@ -1,23 +1,29 @@
export default {
'login.form.title': '登录 ContiNew Admin',
'login.form.subTitle': '持续以最新流行技术栈构建的中后台管理框架',
'login.form.username.placeholder': '请输入用户名',
'login.form.username.errMsg': '请输入用户名',
'login.form.password.placeholder': '请输入密码',
'login.form.password.errMsg': '请输入密码',
'login.form.captcha.placeholder': '请输入验证码',
'login.form.captcha.errMsg': '请输入验证码',
'login.form.placeholder.username': '请输入用户名',
'login.form.placeholder.password': '请输入密码',
'login.form.placeholder.captcha': '请输入验证码',
'login.form.error.required.username': '请输入用户名',
'login.form.error.required.password': '请输入密码',
'login.form.error.required.captcha': '请输入验证码',
'login.form.captcha': '验证码',
'login.form.rememberMe': '记住我',
'login.form.login': '登录',
'login.form.register': '注册账号',
'login.form.forgetPassword': '忘记密码',
'login.form.login.success': '欢迎使用',
'login.form.login.errMsg': '登录出错,请刷新重试',
'login.form.login.error': '登录出错,请刷新重试',
'login.form.logout.success': '退出成功',
'login.banner.slogan1': '中后台管理框架',
'login.banner.subSlogan1': 'Continue New Admin持续以最新流行技术栈构建',
'login.banner.slogan2': '内置了常见问题的解决方案',
'login.banner.subSlogan2': '国际化,路由配置,状态管理应有尽有',
'login.banner.slogan3': '接入可视化增强工具AUX',
'login.banner.subSlogan3': '实现灵活的区块式开发',
'login.banner.subSlogan2': '基础功能丰富,使用门槛低,企业级快速开发脚手架',
'login.banner.slogan3': '代码规范且开源免费',
'login.banner.subSlogan3': '后端代码完全遵循阿里巴巴编码规范,前端代码使用严格的 ESLint 检查',
};

View File

@ -2,23 +2,18 @@
<a-form
ref="formRef"
:model="formData"
:rules="rules"
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')"
:placeholder="$t('userCenter.basicInfo.form.placeholder.username')"
size="large"
max-length="50"
/>
@ -26,16 +21,10 @@
<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')"
:placeholder="$t('userCenter.basicInfo.form.placeholder.nickname')"
size="large"
max-length="32"
/>
@ -53,10 +42,10 @@
<a-form-item>
<a-space>
<a-button type="primary" :loading="loading" @click="save">
{{ $t('userCenter.save') }}
{{ $t('userCenter.basicInfo.form.save') }}
</a-button>
<a-button type="secondary" @click="reset">
{{ $t('userCenter.reset') }}
{{ $t('userCenter.basicInfo.form.reset') }}
</a-button>
</a-space>
</a-form-item>
@ -64,14 +53,16 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useLoginStore } from '@/store';
import { updateBasicInfo } from '@/api/system/user-center';
import useLoading from '@/hooks/loading';
import { FormInstance } from '@arco-design/web-vue/es/form';
import { BasicInfoModel } from '@/api/system/user-center';
import { Message } from "@arco-design/web-vue";
import { FieldRule, Message } from "@arco-design/web-vue";
const { t } = useI18n();
const { loading, setLoading } = useLoading();
const loginStore = useLoginStore();
const formRef = ref<FormInstance>();
@ -80,6 +71,16 @@
nickname: loginStore.nickname,
gender: loginStore.gender,
});
const rules = computed((): Record<string, FieldRule[]> => {
return {
username: [
{ required: true, message: t('userCenter.basicInfo.form.error.required.username') }
],
nickname: [
{ required: true, message: t('userCenter.basicInfo.form.error.required.nickname') }
],
}
});
//
const save = async () => {

View File

@ -1,199 +1,21 @@
<template>
<a-modal v-model:visible="updatePasswordVisible" :title="$t('userCenter.SecuritySettings.form.password.modal.title')" @cancel="handleCancelUpdatePassword" @before-ok="handleBeforeOkUpdatePassword">
<a-form
ref="formPasswordRef"
:model="formPasswordData"
>
<a-form-item
field="oldPassword"
:rules="{ required: true, message: $t('userCenter.SecuritySettings.form.password.oldPassword.placeholder') }"
:validate-trigger="['change', 'blur']"
:label="$t('userCenter.SecuritySettings.form.password.oldPassword.label')"
>
<a-input-password
v-model="formPasswordData.oldPassword"
:placeholder="$t('userCenter.SecuritySettings.form.password.oldPassword.placeholder')"
size="large"
allow-clear
max-length="50"
>
</a-input-password>
</a-form-item>
<a-form-item
field="newPassword"
:rules="[
{
required: true,
message: $t('userCenter.SecuritySettings.form.password.error.newPassword.required'),
},
{
match: /^(?=.*\d)(?=.*[a-z]).{6,32}$/,
message: $t('userCenter.SecuritySettings.form.password.newPassword.placeholder')
}
]"
:validate-trigger="['change', 'blur']"
:label="$t('userCenter.SecuritySettings.form.password.newPassword.label')"
>
<a-input-password
v-model="formPasswordData.newPassword"
:placeholder="$t('userCenter.SecuritySettings.form.password.newPassword.placeholder')"
size="large"
allow-clear
max-length="50"
>
</a-input-password>
</a-form-item>
<a-form-item
field="rePassword"
:rules="[
{
required: true,
message: $t('userCenter.SecuritySettings.form.password.rePassword.placeholder'),
},
{
validator: (value, callback) => {
if (value !== formPasswordData.newPassword) {
callback($t('userCenter.SecuritySettings.form.password.error.rePassword.notequal'))
} else {
callback()
}
}
}
]"
:validate-trigger="['change', 'blur']"
:label="$t('userCenter.SecuritySettings.form.password.rePassword.label')"
>
<a-input-password
v-model="formPasswordData.rePassword"
:placeholder="$t('userCenter.SecuritySettings.form.password.rePassword.placeholder')"
size="large"
allow-clear
max-length="50"
>
</a-input-password>
</a-form-item>
</a-form>
</a-modal>
<a-list :bordered="false">
<a-list-item>
<a-list-item-meta>
<template #avatar>
<a-typography-paragraph>
{{ $t('userCenter.SecuritySettings.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 @click="handleClickUpdatePassword">
{{ $t('userCenter.SecuritySettings.button.update') }}
</a-link>
</div>
</template>
</a-list-item-meta>
<UpdatePwd />
</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>
<UpdatePhone />
</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>
<UpdateEmail />
</a-list-item>
</a-list>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { useLoginStore } from '@/store';
import { FormInstance } from "@arco-design/web-vue/es/form";
import useLoading from "@/hooks/loading";
import { updatePassword } from "@/api/system/user-center";
import { Message } from "@arco-design/web-vue";
import { encryptByRsa } from "@/utils/encrypt";
const { loading, setLoading } = useLoading();
const loginStore = useLoginStore();
const formPasswordRef = ref<FormInstance>();
const updatePasswordVisible = ref(false);
const formPasswordData = reactive({
oldPassword: '',
newPassword: '',
rePassword: '',
});
const handleClickUpdatePassword = () => {
updatePasswordVisible.value = true;
};
const handleBeforeOkUpdatePassword = async () => {
const errors = await formPasswordRef.value?.validate();
if (loading.value) return false;
if (errors) return false;
setLoading(true);
try {
const res = await updatePassword({
oldPassword: encryptByRsa(formPasswordData.oldPassword) || '',
newPassword: encryptByRsa(formPasswordData.newPassword) || '',
});
if (res.success) Message.success(res.msg);
} finally {
setLoading(false);
}
return true;
};
const handleCancelUpdatePassword = () => {
updatePasswordVisible.value = false;
formPasswordRef.value?.resetFields()
};
import UpdatePwd from './security-settings/update-pwd.vue';
import UpdatePhone from './security-settings/update-phone.vue';
import UpdateEmail from './security-settings/update-email.vue';
</script>
<style scoped lang="less">

View File

@ -0,0 +1,32 @@
<template>
<a-list-item-meta>
<template #avatar>
<a-typography-paragraph>
{{ $t('userCenter.securitySettings.updateEmail.label.email') }}
</a-typography-paragraph>
</template>
<template #description>
<div class="content">
<a-typography-paragraph v-if="loginStore.email">
{{ $t('userCenter.securitySettings.updateEmail.placeholder.success.email') }}{{ loginStore.email }}
</a-typography-paragraph>
<a-typography-paragraph v-else class="tip">
{{ $t('userCenter.securitySettings.updateEmail.placeholder.error.email') }}
</a-typography-paragraph>
</div>
<div class="operation">
<a-link>
{{ $t('userCenter.securitySettings.button.update') }}
</a-link>
</div>
</template>
</a-list-item-meta>
</template>
<script lang="ts" setup>
import { useLoginStore } from '@/store';
const loginStore = useLoginStore();
</script>
<style scoped lang="less"></style>

View File

@ -0,0 +1,32 @@
<template>
<a-list-item-meta>
<template #avatar>
<a-typography-paragraph>
{{ $t('userCenter.securitySettings.updatePhone.label.phone') }}
</a-typography-paragraph>
</template>
<template #description>
<div class="content">
<a-typography-paragraph v-if="loginStore.phone">
{{ $t('userCenter.securitySettings.updatePhone.placeholder.success.phone') }}{{ loginStore.phone }}
</a-typography-paragraph>
<a-typography-paragraph v-else class="tip">
{{ $t('userCenter.securitySettings.updatePhone.placeholder.error.phone') }}
</a-typography-paragraph>
</div>
<div class="operation">
<a-link>
{{ $t('userCenter.securitySettings.button.update') }}
</a-link>
</div>
</template>
</a-list-item-meta>
</template>
<script lang="ts" setup>
import { useLoginStore } from '@/store';
const loginStore = useLoginStore();
</script>
<style scoped lang="less"></style>

View File

@ -0,0 +1,160 @@
<template>
<a-list-item-meta>
<template #avatar>
<a-typography-paragraph>
{{ $t('userCenter.securitySettings.updatePwd.label.password') }}
</a-typography-paragraph>
</template>
<template #description>
<div class="content">
<a-typography-paragraph v-if="loginStore.pwdResetTime">
{{ $t('userCenter.securitySettings.updatePwd.placeholder.success.password') }}
</a-typography-paragraph>
<a-typography-paragraph v-else class="tip">
{{ $t('userCenter.securitySettings.updatePwd.placeholder.error.password') }}
</a-typography-paragraph>
</div>
<div class="operation">
<a-link @click="toUpdate">
{{ $t('userCenter.securitySettings.button.update') }}
</a-link>
</div>
</template>
</a-list-item-meta>
<a-modal v-model:visible="visible" :title="$t('userCenter.securitySettings.updatePwd.modal.title')" @cancel="handleCancel" @before-ok="handleUpdate">
<a-form
ref="formRef"
:model="formData"
:rules="rules"
>
<a-form-item
field="oldPassword"
:validate-trigger="['change', 'blur']"
:label="$t('userCenter.securitySettings.updatePwd.form.label.oldPassword')"
>
<a-input-password
v-model="formData.oldPassword"
:placeholder="$t('userCenter.securitySettings.updatePwd.form.placeholder.oldPassword')"
size="large"
allow-clear
max-length="50"
>
</a-input-password>
</a-form-item>
<a-form-item
field="newPassword"
:validate-trigger="['change', 'blur']"
:label="$t('userCenter.securitySettings.updatePwd.form.label.newPassword')"
>
<a-input-password
v-model="formData.newPassword"
:placeholder="$t('userCenter.securitySettings.updatePwd.form.placeholder.newPassword')"
size="large"
allow-clear
max-length="50"
>
</a-input-password>
</a-form-item>
<a-form-item
field="rePassword"
:validate-trigger="['change', 'blur']"
:label="$t('userCenter.securitySettings.updatePwd.form.label.rePassword')"
>
<a-input-password
v-model="formData.rePassword"
:placeholder="$t('userCenter.securitySettings.updatePwd.form.placeholder.rePassword')"
size="large"
allow-clear
max-length="50"
>
</a-input-password>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useLoginStore } from '@/store';
import { FormInstance } from "@arco-design/web-vue/es/form";
import useLoading from "@/hooks/loading";
import { FieldRule, Message } from "@arco-design/web-vue";
import { updatePassword } from "@/api/system/user-center";
import { encryptByRsa } from "@/utils/encrypt";
const { t } = useI18n();
const { loading, setLoading } = useLoading();
const loginStore = useLoginStore();
const visible = ref(false);
const formRef = ref<FormInstance>();
const formData = reactive({
oldPassword: '',
newPassword: '',
rePassword: '',
});
const rules = computed((): Record<string, FieldRule[]> => {
return {
oldPassword: [
{ required: true, message: t('userCenter.securitySettings.updatePwd.form.error.required.oldPassword') }
],
newPassword: [
{ required: true, message: t('userCenter.securitySettings.updatePwd.form.error.required.newPassword') },
{ match: /^(?=.*\d)(?=.*[a-z]).{6,32}$/, message: t('userCenter.securitySettings.updatePwd.form.error.match.newPassword') },
{
validator: (value, callback) => {
if (value === formData.oldPassword) {
callback(t('userCenter.securitySettings.updatePwd.form.error.validator.newPassword'))
} else {
callback()
}
}
}
],
rePassword: [
{ required: true, message: t('userCenter.securitySettings.updatePwd.form.error.required.rePassword') },
{
validator: (value, callback) => {
if (value !== formData.newPassword) {
callback(t('userCenter.securitySettings.updatePwd.form.error.validator.rePassword'))
} else {
callback()
}
}
}
],
}
});
//
const handleUpdate = async () => {
const errors = await formRef.value?.validate();
if (loading.value) return false;
if (errors) return false;
setLoading(true);
try {
const res = await updatePassword({
oldPassword: encryptByRsa(formData.oldPassword) || '',
newPassword: encryptByRsa(formData.newPassword) || '',
});
if (res.success) Message.success(res.msg);
} finally {
setLoading(false);
}
return true;
};
//
const handleCancel = () => {
visible.value = false;
formRef.value?.resetFields()
};
//
const toUpdate = () => {
visible.value = true;
};
</script>
<style scoped lang="less"></style>

View File

@ -14,7 +14,7 @@
<template #trigger-icon>
<icon-camera />
</template>
<img v-if="avatarList.length" :src="avatarList[0].url" />
<img v-if="avatarList.length" :src="avatarList[0].url" :alt="$t('userCenter.panel.avatar')"/>
</a-avatar>
</template>
</a-upload>
@ -33,21 +33,21 @@
textAlign: 'left',
}"
>
<a-descriptions-item :label="$t('userCenter.label.nickname')">{{ loginStore.nickname }}</a-descriptions-item>
<a-descriptions-item :label="$t('userCenter.label.gender')">
<a-descriptions-item :label="$t('userCenter.panel.label.nickname')">{{ loginStore.nickname }}</a-descriptions-item>
<a-descriptions-item :label="$t('userCenter.panel.label.gender')">
<div v-if="loginStore.gender === 1">
{{ $t('userCenter.panel.male') }}
<icon-man style="color: #19BBF1" />
</div>
<div v-else-if="loginStore.gender === 2">
{{ $t('userCenter.panel.female') }}
<icon-woman style="color: #FA7FA9" />
</div>
<div v-else>未知</div>
<div v-else>{{ $t('userCenter.panel.unknown') }}</div>
</a-descriptions-item>
<a-descriptions-item :label="$t('userCenter.label.phone')">{{ loginStore.phone }}</a-descriptions-item>
<a-descriptions-item :label="$t('userCenter.label.email')">{{ loginStore.email }}</a-descriptions-item>
<a-descriptions-item :label="$t('userCenter.label.registrationDate')">{{ loginStore.registrationDate }}</a-descriptions-item>
<a-descriptions-item :label="$t('userCenter.panel.label.phone')">{{ loginStore.phone }}</a-descriptions-item>
<a-descriptions-item :label="$t('userCenter.panel.label.email')">{{ loginStore.email }}</a-descriptions-item>
<a-descriptions-item :label="$t('userCenter.panel.label.registrationDate')">{{ loginStore.registrationDate }}</a-descriptions-item>
</a-descriptions>
</a-space>
</a-card>

View File

@ -9,8 +9,8 @@
<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 key="1" :title="$t('userCenter.tab.basicInfo')">
<BasicInfo />
</a-tab-pane>
<a-tab-pane key="2" :title="$t('userCenter.tab.securitySettings')">
<SecuritySettings />
@ -23,7 +23,7 @@
<script lang="ts" setup>
import UserPanel from './components/user-panel.vue';
import BasicInformation from './components/basic-information.vue';
import BasicInfo from './components/basic-info.vue';
import SecuritySettings from './components/security-settings.vue';
</script>

View File

@ -1,41 +1,67 @@
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.basicInfo': 'Basic Information',
'userCenter.tab.securitySettings': 'Security Settings',
'userCenter.SecuritySettings.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.password.modal.title': 'Update login password',
'userCenter.SecuritySettings.form.password.oldPassword.label': 'Old password',
'userCenter.SecuritySettings.form.password.oldPassword.placeholder': 'Please enter old password',
'userCenter.SecuritySettings.form.password.newPassword.label': 'New password',
'userCenter.SecuritySettings.form.password.error.newPassword.required': 'Please enter new password',
'userCenter.SecuritySettings.form.password.newPassword.placeholder': 'Password contains 6 to 32 digits and letters',
'userCenter.SecuritySettings.form.password.rePassword.label': 'Confirm password',
'userCenter.SecuritySettings.form.password.rePassword.placeholder': 'Please enter new password again',
'userCenter.SecuritySettings.form.password.error.rePassword.notequal': 'Two passwords are different',
'userCenter.SecuritySettings.form.label.phone': 'Phone',
'userCenter.SecuritySettings.placeholder.phone':
// user-panel
'userCenter.panel.avatar': 'Avatar',
'userCenter.panel.label.nickname': 'Nick Name :',
'userCenter.panel.label.gender': 'Gender :',
'userCenter.panel.label.phone': 'Phone :',
'userCenter.panel.label.email': 'Email :',
'userCenter.panel.label.registrationDate': 'Registration Date :',
'userCenter.panel.male': 'male',
'userCenter.panel.female': 'female',
'userCenter.panel.unknown': 'unknown',
// basic-info
'userCenter.basicInfo.form.label.username': 'Username',
'userCenter.basicInfo.form.label.nickname': 'Nickname',
'userCenter.basicInfo.form.label.gender': 'Gender',
'userCenter.basicInfo.form.placeholder.username': 'Please enter username',
'userCenter.basicInfo.form.placeholder.nickname': 'Please enter nickname',
'userCenter.basicInfo.form.error.required.username': 'Please enter username',
'userCenter.basicInfo.form.error.required.nickname': 'Please enter nickname',
'userCenter.basicInfo.form.save': 'Save',
'userCenter.basicInfo.form.reset': 'Reset',
// security-settings
// update-pwd
'userCenter.securitySettings.updatePwd.label.password': 'Login Password',
'userCenter.securitySettings.updatePwd.placeholder.success.password': 'Has been set',
'userCenter.securitySettings.updatePwd.placeholder.error.password':
'You have not set a password yet. The password must contain at least six letters, digits, and special characters except Spaces.',
'userCenter.securitySettings.updatePwd.modal.title': 'Update login password',
'userCenter.securitySettings.updatePwd.form.label.oldPassword': 'Old password',
'userCenter.securitySettings.updatePwd.form.label.newPassword': 'New password',
'userCenter.securitySettings.updatePwd.form.label.rePassword': 'Confirm password',
'userCenter.securitySettings.updatePwd.form.placeholder.oldPassword': 'Please enter old password',
'userCenter.securitySettings.updatePwd.form.placeholder.newPassword': 'Password contains 6 to 32 digits and letters',
'userCenter.securitySettings.updatePwd.form.placeholder.rePassword': 'Please enter new password again',
'userCenter.securitySettings.updatePwd.form.error.required.oldPassword': 'Please enter old password',
'userCenter.securitySettings.updatePwd.form.error.required.newPassword': 'Please enter new password',
'userCenter.securitySettings.updatePwd.form.error.match.newPassword': 'Password contains 6 to 32 digits and letters',
'userCenter.securitySettings.updatePwd.form.error.validator.newPassword': 'New password cannot be the same as the old password',
'userCenter.securitySettings.updatePwd.form.error.required.rePassword': 'Please enter new password again',
'userCenter.securitySettings.updatePwd.form.error.validator.rePassword': 'Two passwords are different',
// update-phone
'userCenter.securitySettings.updatePhone.label.phone': 'Phone',
'userCenter.securitySettings.updatePhone.placeholder.success.phone': 'Has been bound',
'userCenter.securitySettings.updatePhone.placeholder.error.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':
// update-email
'userCenter.securitySettings.updateEmail.label.email': 'Email',
'userCenter.securitySettings.updateEmail.placeholder.success.email': 'Has been bound',
'userCenter.securitySettings.updateEmail.placeholder.error.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',
'userCenter.securitySettings.button.update': 'Update',
};

View File

@ -1,41 +1,67 @@
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.basicInfo': '基础信息',
'userCenter.tab.securitySettings': '安全设置',
'userCenter.SecuritySettings.label.password': '登录密码',
'userCenter.SecuritySettings.placeholder.password':
'您暂未设置密码密码至少6位字符支持数字、字母和除空格外的特殊字符。',
'userCenter.SecuritySettings.form.password.modal.title': '修改登录密码',
'userCenter.SecuritySettings.form.password.oldPassword.label': '当前密码',
'userCenter.SecuritySettings.form.password.oldPassword.placeholder': '请输入当前密码',
'userCenter.SecuritySettings.form.password.newPassword.label': '新密码',
'userCenter.SecuritySettings.form.password.error.newPassword.required': '请输入新密码',
'userCenter.SecuritySettings.form.password.newPassword.placeholder': '密码长度6到32位同时包含数字和字母',
'userCenter.SecuritySettings.form.password.rePassword.label': '确认新密码',
'userCenter.SecuritySettings.form.password.rePassword.placeholder': '请再次输入新密码',
'userCenter.SecuritySettings.form.password.error.rePassword.notequal': '两次输入的密码不一致',
'userCenter.SecuritySettings.form.label.phone': '安全手机',
'userCenter.SecuritySettings.placeholder.phone':
// user-panel
'userCenter.panel.avatar': '头像',
'userCenter.panel.label.nickname': '昵称 :',
'userCenter.panel.label.gender': '性别 :',
'userCenter.panel.label.phone': '手机号码 :',
'userCenter.panel.label.email': '邮箱 :',
'userCenter.panel.label.registrationDate': '注册日期 :',
'userCenter.panel.male': '男',
'userCenter.panel.female': '女',
'userCenter.panel.unknown': '未知',
// basic-info
'userCenter.basicInfo.form.label.username': '用户名',
'userCenter.basicInfo.form.label.nickname': '昵称',
'userCenter.basicInfo.form.label.gender': '性别',
'userCenter.basicInfo.form.placeholder.username': '请输入用户名',
'userCenter.basicInfo.form.placeholder.nickname': '请输入昵称',
'userCenter.basicInfo.form.error.required.username': '请输入用户名',
'userCenter.basicInfo.form.error.required.nickname': '请输入昵称',
'userCenter.basicInfo.form.save': '保存',
'userCenter.basicInfo.form.reset': '重置',
// security-settings
// update-pwd
'userCenter.securitySettings.updatePwd.label.password': '登录密码',
'userCenter.securitySettings.updatePwd.placeholder.success.password': '已设置',
'userCenter.securitySettings.updatePwd.placeholder.error.password':
'您暂未设置密码密码至少6位字符支持数字、字母和除空格外的特殊字符。',
'userCenter.securitySettings.updatePwd.modal.title': '修改登录密码',
'userCenter.securitySettings.updatePwd.form.label.oldPassword': '当前密码',
'userCenter.securitySettings.updatePwd.form.label.newPassword': '新密码',
'userCenter.securitySettings.updatePwd.form.label.rePassword': '确认新密码',
'userCenter.securitySettings.updatePwd.form.placeholder.oldPassword': '请输入当前密码',
'userCenter.securitySettings.updatePwd.form.placeholder.newPassword': '密码长度 6 到 32 位,同时包含数字和字母',
'userCenter.securitySettings.updatePwd.form.placeholder.rePassword': '请再次输入新密码',
'userCenter.securitySettings.updatePwd.form.error.required.oldPassword': '请输入当前密码',
'userCenter.securitySettings.updatePwd.form.error.required.newPassword': '请输入新密码',
'userCenter.securitySettings.updatePwd.form.error.match.newPassword': '密码长度 6 到 32 位,同时包含数字和字母',
'userCenter.securitySettings.updatePwd.form.error.validator.newPassword': '新密码不能与当前密码相同',
'userCenter.securitySettings.updatePwd.form.error.required.rePassword': '请再次输入新密码',
'userCenter.securitySettings.updatePwd.form.error.validator.rePassword': '两次输入的密码不一致',
// update-phone
'userCenter.securitySettings.updatePhone.label.phone': '安全手机',
'userCenter.securitySettings.updatePhone.placeholder.success.phone': '已绑定',
'userCenter.securitySettings.updatePhone.placeholder.error.phone':
'您暂未设置手机号,绑定手机号可以用来找回密码、接收通知、短信登录等。',
'userCenter.SecuritySettings.form.label.email': '安全邮箱',
'userCenter.SecuritySettings.placeholder.email':
// update-email
'userCenter.securitySettings.updateEmail.label.email': '安全邮箱',
'userCenter.securitySettings.updateEmail.placeholder.success.email': '已绑定',
'userCenter.securitySettings.updateEmail.placeholder.error.email':
'您暂未设置邮箱,绑定邮箱可以用来找回密码、接收通知等。',
'userCenter.SecuritySettings.button.update': '修改',
'userCenter.securitySettings.button.update': '修改',
};

View File

@ -105,6 +105,7 @@ public class UserCenterController {
// 校验
ValidationUtils.exIfCondition(() -> !ReUtil.isMatch(RegExpConstants.PASSWORD, rawNewPassword),
"密码长度 6 到 32 位,同时包含数字和字母");
ValidationUtils.exIfEqual(rawNewPassword, rawOldPassword, "新密码不能与当前密码相同");
// 修改密码
userService.updatePassword(rawOldPassword, rawNewPassword, LoginHelper.getUserId());