feat: 邮箱密码修改
This commit is contained in:
@@ -63,15 +63,6 @@ export const fetchDownloadItemsByGet = (params: Download): Promise<ApiResponse>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserProfile {
|
|
||||||
firstName: string
|
|
||||||
lastName: string
|
|
||||||
username: string
|
|
||||||
roles: string[]
|
|
||||||
region: string
|
|
||||||
language: string
|
|
||||||
email: string
|
|
||||||
}
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
export const fetchUserProfile = (): Promise<ApiResponse> => {
|
export const fetchUserProfile = (): Promise<ApiResponse> => {
|
||||||
return request({
|
return request({
|
||||||
@@ -81,6 +72,18 @@ export const fetchUserProfile = (): Promise<ApiResponse> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 设置用户信息
|
// 设置用户信息
|
||||||
|
export interface UserProfile {
|
||||||
|
firstName: string
|
||||||
|
lastName: string
|
||||||
|
username: string
|
||||||
|
roles: string[]
|
||||||
|
region: string
|
||||||
|
language: string
|
||||||
|
email: string
|
||||||
|
oldPassword?: string
|
||||||
|
newPassword?: string
|
||||||
|
verifyCode?: string
|
||||||
|
}
|
||||||
export const updateUserProfile = (data: UserProfile): Promise<ApiResponse> => {
|
export const updateUserProfile = (data: UserProfile): Promise<ApiResponse> => {
|
||||||
return request({
|
return request({
|
||||||
url: '/buyer/profile/setProfile',
|
url: '/buyer/profile/setProfile',
|
||||||
@@ -88,3 +91,20 @@ export const updateUserProfile = (data: UserProfile): Promise<ApiResponse> => {
|
|||||||
data
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取设置页验证码
|
||||||
|
export const fetchVerifyCode = (): Promise<ApiResponse> => {
|
||||||
|
return request({
|
||||||
|
url: '/buyer/profile/sendEmailChangeCode',
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证设置页验证码
|
||||||
|
export const verifyEmailCode = (verifyCode: string): Promise<ApiResponse> => {
|
||||||
|
return request({
|
||||||
|
url: '/buyer/profile/verifyEmailChangeCode',
|
||||||
|
method: 'post',
|
||||||
|
data: { verifyCode }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ export default {
|
|||||||
discard: 'DISCARD',
|
discard: 'DISCARD',
|
||||||
edit: 'EDIT',
|
edit: 'EDIT',
|
||||||
saveChange: 'SAVE CHANGE',
|
saveChange: 'SAVE CHANGE',
|
||||||
|
verifyEmail: 'VERIFY EMAIL',
|
||||||
saving: 'SAVING...'
|
saving: 'SAVING...'
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {
|
||||||
@@ -117,7 +118,7 @@ export default {
|
|||||||
resendCodeIn: 'Resend Code in {time}'
|
resendCodeIn: 'Resend Code in {time}'
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
enterNewEmailFirst: 'Please enter your new email address first',
|
enterNewEmailFirst: 'Please enter your email address first',
|
||||||
invalidEmail: 'Please enter a valid email address',
|
invalidEmail: 'Please enter a valid email address',
|
||||||
sameEmail: 'Please enter a different email address',
|
sameEmail: 'Please enter a different email address',
|
||||||
alreadyVerified: 'This email has already been verified',
|
alreadyVerified: 'This email has already been verified',
|
||||||
@@ -125,6 +126,11 @@ export default {
|
|||||||
enterVerificationCode: 'Please enter the 6-digit verification code',
|
enterVerificationCode: 'Please enter the 6-digit verification code',
|
||||||
verificationCompleted: 'Email verification completed',
|
verificationCompleted: 'Email verification completed',
|
||||||
verifyEmailBeforeSave: 'Please verify your new email before saving',
|
verifyEmailBeforeSave: 'Please verify your new email before saving',
|
||||||
|
currentPasswordRequired: 'Please enter your current password',
|
||||||
|
passwordLengthError: 'Password length must be between {min} and {max} characters',
|
||||||
|
passwordSpecial: 'Password must contain special characters',
|
||||||
|
passwordCase: 'Password must include upper/lowercase letters and numbers',
|
||||||
|
passwordNotSameAsOld: 'New password cannot be the same as current password',
|
||||||
settingsUpdated: 'Settings updated'
|
settingsUpdated: 'Settings updated'
|
||||||
},
|
},
|
||||||
roles: {
|
roles: {
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ export default {
|
|||||||
discard: '放弃',
|
discard: '放弃',
|
||||||
edit: '编辑',
|
edit: '编辑',
|
||||||
saveChange: '保存更改',
|
saveChange: '保存更改',
|
||||||
|
verifyEmail: '验证邮箱',
|
||||||
saving: '保存中...'
|
saving: '保存中...'
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {
|
||||||
@@ -122,6 +123,11 @@ export default {
|
|||||||
enterVerificationCode: '请输入 6 位验证码',
|
enterVerificationCode: '请输入 6 位验证码',
|
||||||
verificationCompleted: '邮箱验证完成',
|
verificationCompleted: '邮箱验证完成',
|
||||||
verifyEmailBeforeSave: '请先完成新邮箱验证再保存',
|
verifyEmailBeforeSave: '请先完成新邮箱验证再保存',
|
||||||
|
currentPasswordRequired: '请输入当前密码',
|
||||||
|
passwordLengthError: '密码长度必须在 {min} 到 {max} 个字符之间',
|
||||||
|
passwordSpecial: '密码必须包含特殊符号',
|
||||||
|
passwordCase: '密码必须包含大小写字母和数字',
|
||||||
|
passwordNotSameAsOld: '新密码不能与旧密码相同',
|
||||||
settingsUpdated: '设置已更新'
|
settingsUpdated: '设置已更新'
|
||||||
},
|
},
|
||||||
roles: {
|
roles: {
|
||||||
|
|||||||
@@ -32,9 +32,9 @@
|
|||||||
{{ isEmailVerified ? t('Settings.security.verified') : t('Settings.security.verify') }}
|
{{ isEmailVerified ? t('Settings.security.verified') : t('Settings.security.verify') }}
|
||||||
</button> -->
|
</button> -->
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isEmailVerified" class="security-tip verified-tip">
|
<!-- <div v-if="isEmailVerified" class="security-tip verified-tip">
|
||||||
{{ t('Settings.security.verifiedTip') }}
|
{{ t('Settings.security.verifiedTip') }}
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inner-divider" />
|
<div class="inner-divider" />
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="action-container">
|
<div class="action-container">
|
||||||
<template v-if="isEditing">
|
<template v-if="isEditing">
|
||||||
<button type="button" class="primary-btn" :disabled="saving" @click="emit('save')">
|
<button v-if="needsEmailVerification" type="button" class="primary-btn" :disabled="saving" @click="emit('verify')">
|
||||||
|
{{ t('Settings.buttons.verifyEmail') }}
|
||||||
|
</button>
|
||||||
|
<button v-else type="button" class="primary-btn" :disabled="saving" @click="emit('save')">
|
||||||
{{ saving ? t('Settings.buttons.saving') : t('Settings.buttons.saveChange') }}
|
{{ saving ? t('Settings.buttons.saving') : t('Settings.buttons.saveChange') }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="secondary-btn" :disabled="saving" @click="emit('discard')">
|
<button type="button" class="secondary-btn" :disabled="saving" @click="emit('discard')">
|
||||||
@@ -22,11 +25,13 @@ import { useI18n } from 'vue-i18n'
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
isEditing: boolean
|
isEditing: boolean
|
||||||
saving: boolean
|
saving: boolean
|
||||||
|
needsEmailVerification: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'edit'): void
|
(event: 'edit'): void
|
||||||
(event: 'save'): void
|
(event: 'save'): void
|
||||||
|
(event: 'verify'): void
|
||||||
(event: 'discard'): void
|
(event: 'discard'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -48,8 +48,10 @@
|
|||||||
<SettingsActions
|
<SettingsActions
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
:saving="saving"
|
:saving="saving"
|
||||||
|
:needs-email-verification="needsEmailVerification"
|
||||||
@edit="handleEdit"
|
@edit="handleEdit"
|
||||||
@save="handleSave"
|
@save="handleSave"
|
||||||
|
@verify="handleVerifyEmail"
|
||||||
@discard="handleDiscard"
|
@discard="handleDiscard"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,7 +60,7 @@
|
|||||||
|
|
||||||
<EmailVerificationDialog
|
<EmailVerificationDialog
|
||||||
:visible="isVerificationDialogVisible"
|
:visible="isVerificationDialogVisible"
|
||||||
:email="verificationTargetEmail"
|
:email="displayData.email"
|
||||||
:saving="saving"
|
:saving="saving"
|
||||||
@close="closeVerificationDialog"
|
@close="closeVerificationDialog"
|
||||||
@resend="handleSendVerifyCode"
|
@resend="handleSendVerifyCode"
|
||||||
@@ -95,6 +97,7 @@
|
|||||||
displayRegionLabel,
|
displayRegionLabel,
|
||||||
fullName,
|
fullName,
|
||||||
roleModel,
|
roleModel,
|
||||||
|
needsEmailVerification,
|
||||||
handleEdit,
|
handleEdit,
|
||||||
handleDiscard,
|
handleDiscard,
|
||||||
handleSave,
|
handleSave,
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { computed, ref, shallowRef, watch, type Ref } from 'vue'
|
import { computed, ref, shallowRef, watch, type Ref } from 'vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { fetchUserProfile, type UserProfile, updateUserProfile } from '@/api/user'
|
import md5 from 'md5'
|
||||||
|
import {
|
||||||
|
fetchUserProfile,
|
||||||
|
type UserProfile,
|
||||||
|
updateUserProfile,
|
||||||
|
verifyEmailCode,
|
||||||
|
fetchVerifyCode
|
||||||
|
} from '@/api/user'
|
||||||
import regionList from '@/utils/area'
|
import regionList from '@/utils/area'
|
||||||
import {
|
import {
|
||||||
languageValues,
|
languageValues,
|
||||||
@@ -10,6 +17,7 @@ import {
|
|||||||
type SecurityDraft,
|
type SecurityDraft,
|
||||||
type SettingsData
|
type SettingsData
|
||||||
} from './types'
|
} from './types'
|
||||||
|
import { validateCase, validateLength, validateSpecial } from '@/views/login/tools'
|
||||||
|
|
||||||
type Translate = (key: string, ...args: unknown[]) => string
|
type Translate = (key: string, ...args: unknown[]) => string
|
||||||
|
|
||||||
@@ -79,6 +87,7 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
|||||||
const isVerificationDialogVisible = shallowRef(false)
|
const isVerificationDialogVisible = shallowRef(false)
|
||||||
const verificationTargetEmail = shallowRef('')
|
const verificationTargetEmail = shallowRef('')
|
||||||
const verifiedEmail = shallowRef('')
|
const verifiedEmail = shallowRef('')
|
||||||
|
const verificationCode = shallowRef('')
|
||||||
|
|
||||||
const roleList = computed(() =>
|
const roleList = computed(() =>
|
||||||
roleValues.map((value) => ({
|
roleValues.map((value) => ({
|
||||||
@@ -104,6 +113,10 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
|||||||
const isEmailVerified = computed(
|
const isEmailVerified = computed(
|
||||||
() => hasNewEmailChange.value && verifiedEmail.value === normalizedNewEmail.value
|
() => hasNewEmailChange.value && verifiedEmail.value === normalizedNewEmail.value
|
||||||
)
|
)
|
||||||
|
const hasNewPasswordChange = computed(() => securityDraft.value.newPassword.length > 0)
|
||||||
|
const needsEmailVerification = computed(
|
||||||
|
() => (hasNewEmailChange.value || hasNewPasswordChange.value) && !isEmailVerified.value
|
||||||
|
)
|
||||||
const displayLanguageLabel = computed(() =>
|
const displayLanguageLabel = computed(() =>
|
||||||
displayData.value.language ? t(`Settings.languages.${displayData.value.language}`) : ''
|
displayData.value.language ? t(`Settings.languages.${displayData.value.language}`) : ''
|
||||||
)
|
)
|
||||||
@@ -137,6 +150,7 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
|||||||
isVerificationDialogVisible.value = false
|
isVerificationDialogVisible.value = false
|
||||||
verificationTargetEmail.value = ''
|
verificationTargetEmail.value = ''
|
||||||
verifiedEmail.value = ''
|
verifiedEmail.value = ''
|
||||||
|
verificationCode.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncAppLanguage = (language: LanguageValue) => {
|
const syncAppLanguage = (language: LanguageValue) => {
|
||||||
@@ -192,41 +206,67 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleVerifyEmail = () => {
|
const handleVerifyEmail = () => {
|
||||||
|
if (!hasNewEmailChange.value && !hasNewPasswordChange.value) return
|
||||||
|
|
||||||
const nextEmail = normalizedNewEmail.value
|
const nextEmail = normalizedNewEmail.value
|
||||||
|
const newPassword = securityDraft.value.newPassword
|
||||||
|
const currentPassword = securityDraft.value.currentPassword
|
||||||
|
let targetEmail = ''
|
||||||
|
|
||||||
if (!nextEmail) {
|
if (hasNewPasswordChange.value) {
|
||||||
ElMessage.warning(t('Settings.messages.enterNewEmailFirst'))
|
if (validateLength(newPassword)) {
|
||||||
return
|
ElMessage.warning(t('Settings.messages.passwordLengthError', { min: 6, max: 20 }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validateSpecial(newPassword)) {
|
||||||
|
ElMessage.warning(t('Settings.messages.passwordSpecial'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validateCase(newPassword)) {
|
||||||
|
ElMessage.warning(t('Settings.messages.passwordCase'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword === currentPassword) {
|
||||||
|
ElMessage.warning(t('Settings.messages.passwordNotSameAsOld'))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!emailPattern.test(nextEmail)) {
|
if (hasNewEmailChange.value) {
|
||||||
ElMessage.warning(t('Settings.messages.invalidEmail'))
|
if (!emailPattern.test(nextEmail)) {
|
||||||
return
|
ElMessage.warning(t('Settings.messages.invalidEmail'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextEmail === sourceData.value.email) {
|
||||||
|
ElMessage.warning(t('Settings.messages.sameEmail'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verifiedEmail.value === nextEmail) {
|
||||||
|
ElMessage.success(t('Settings.messages.alreadyVerified'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetEmail = nextEmail
|
||||||
|
} else if (hasNewPasswordChange.value) {
|
||||||
|
targetEmail = sourceData.value.email
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextEmail === sourceData.value.email) {
|
if (!targetEmail) return
|
||||||
ElMessage.warning(t('Settings.messages.sameEmail'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verifiedEmail.value === nextEmail) {
|
verificationTargetEmail.value = targetEmail
|
||||||
ElMessage.success(t('Settings.messages.alreadyVerified'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
verificationTargetEmail.value = nextEmail
|
|
||||||
handleSendVerifyCode()
|
handleSendVerifyCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSendVerifyCode = () => {
|
const handleSendVerifyCode = () => {
|
||||||
// AccountSendVerifyCode({
|
fetchVerifyCode().then(() => {
|
||||||
// email: verificationTargetEmail.value,
|
ElMessage.success(t('Settings.messages.verificationCodeSent'))
|
||||||
// operationType: 'FORGET_PWD'
|
isVerificationDialogVisible.value = true
|
||||||
// }).then((res) => {
|
})
|
||||||
// console.log(res)
|
|
||||||
// ElMessage.success(t('Settings.messages.verificationCodeSent'))
|
|
||||||
// isVerificationDialogVisible.value = true
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleVerificationSubmit = (code: string) => {
|
const handleVerificationSubmit = (code: string) => {
|
||||||
@@ -234,24 +274,13 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
|||||||
ElMessage.warning(t('Settings.messages.enterVerificationCode'))
|
ElMessage.warning(t('Settings.messages.enterVerificationCode'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// send code to backend and store the code locally so save() can include it
|
||||||
verifiedEmail.value = verificationTargetEmail.value
|
verifyEmailCode(code).then((res) => {
|
||||||
closeVerificationDialog()
|
verificationCode.value = code
|
||||||
ElMessage.success(t('Settings.messages.verificationCompleted'))
|
verifiedEmail.value = verificationTargetEmail.value
|
||||||
}
|
closeVerificationDialog()
|
||||||
|
ElMessage.success(t('Settings.messages.verificationCompleted'))
|
||||||
const buildNextData = (): SettingsData => {
|
})
|
||||||
const nextEmail = securityDraft.value.newEmail.trim() || draftData.value.email
|
|
||||||
|
|
||||||
return {
|
|
||||||
firstName: draftData.value.firstName.trim(),
|
|
||||||
lastName: draftData.value.lastName.trim(),
|
|
||||||
username: draftData.value.username.trim(),
|
|
||||||
email: nextEmail,
|
|
||||||
roles: [...draftData.value.roles],
|
|
||||||
language: draftData.value.language,
|
|
||||||
region: draftData.value.region
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
@@ -260,13 +289,47 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextData = buildNextData()
|
if (hasNewPasswordChange.value && !verificationCode.value) {
|
||||||
|
ElMessage.warning(t('Settings.messages.verifyEmailBeforeSave'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextEmail = securityDraft.value.newEmail.trim() || draftData.value.email
|
||||||
|
const nextData: UserProfile = {
|
||||||
|
firstName: draftData.value.firstName.trim(),
|
||||||
|
lastName: draftData.value.lastName.trim(),
|
||||||
|
username: draftData.value.username.trim(),
|
||||||
|
email: nextEmail,
|
||||||
|
roles: draftData.value.roles as string[],
|
||||||
|
language: draftData.value.language,
|
||||||
|
region: draftData.value.region
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果改邮箱或改密码,需要添加验证码和密码信息
|
||||||
|
if (hasNewEmailChange.value || hasNewPasswordChange.value) {
|
||||||
|
nextData.verifyCode = verificationCode.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasNewPasswordChange.value) {
|
||||||
|
nextData.oldPassword = md5(securityDraft.value.currentPassword)
|
||||||
|
nextData.newPassword = md5(securityDraft.value.newPassword)
|
||||||
|
}
|
||||||
|
|
||||||
const previousLanguage = sourceData.value.language
|
const previousLanguage = sourceData.value.language
|
||||||
saving.value = true
|
saving.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateUserProfile(nextData)
|
await updateUserProfile(nextData)
|
||||||
sourceData.value = cloneSettingsData(nextData)
|
const settingsData: SettingsData = {
|
||||||
|
firstName: nextData.firstName,
|
||||||
|
lastName: nextData.lastName,
|
||||||
|
username: nextData.username,
|
||||||
|
email: nextData.email,
|
||||||
|
roles: nextData.roles as RoleValue[],
|
||||||
|
language: nextData.language as LanguageValue | '',
|
||||||
|
region: nextData.region as any
|
||||||
|
}
|
||||||
|
sourceData.value = cloneSettingsData(settingsData)
|
||||||
console.log(nextData)
|
console.log(nextData)
|
||||||
|
|
||||||
if (nextData.language && nextData.language !== previousLanguage) {
|
if (nextData.language && nextData.language !== previousLanguage) {
|
||||||
@@ -321,6 +384,7 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
|||||||
displayRegionLabel,
|
displayRegionLabel,
|
||||||
fullName,
|
fullName,
|
||||||
roleModel,
|
roleModel,
|
||||||
|
needsEmailVerification,
|
||||||
handleEdit,
|
handleEdit,
|
||||||
handleDiscard,
|
handleDiscard,
|
||||||
handleSave,
|
handleSave,
|
||||||
|
|||||||
Reference in New Issue
Block a user