feat: security部分改为二次编辑

This commit is contained in:
2026-05-28 11:46:23 +08:00
parent 729755efdf
commit 881f25ac2b
3 changed files with 262 additions and 200 deletions

View File

@@ -1,29 +1,42 @@
<template> <template>
<SettingsSection <SettingsSection
:title="t('Settings.security.title')" :title="t('Settings.security.title')"
:description="t('Settings.security.description')" :description="t('Settings.security.description')"
content-class="security-container" content-class="security-container"
> >
<div class="inner-divider" /> <div class="inner-divider" />
<div class="security-row"> <div class="security-row">
<div class="security-inline-row"> <div class="security-inline-row">
<div class="security-label inline">{{ t('Settings.security.email') }}</div> <div class="security-label inline">{{ t('Settings.security.email') }}</div>
<div class="security-static">{{ email }}</div> <div class="security-static">{{ email }}</div>
<button v-show="isEditing" type="button" class="small-btn" @click="emit('reset-email')"> <button
{{ t('Settings.buttons.cancel') }} v-show="isEditing && !isEditingEmail"
</button> type="button"
</div> class="small-btn"
</div> @click="emit('edit-email')"
>
{{ t('Settings.buttons.edit') }}
</button>
<button
v-show="isEditing && isEditingEmail"
type="button"
class="small-btn"
@click="emit('reset-email')"
>
{{ t('Settings.buttons.cancel') }}
</button>
</div>
</div>
<div v-show="isEditing" class="security-row"> <div v-show="isEditing && isEditingEmail" class="security-row">
<div class="security-label">{{ t('Settings.security.newEmail') }}</div> <div class="security-label">{{ t('Settings.security.newEmail') }}</div>
<div class="outlined-field verify-field"> <div class="outlined-field verify-field">
<el-input <el-input
:model-value="newEmail" :model-value="newEmail"
:placeholder="t('Settings.security.newEmailPlaceholder')" :placeholder="t('Settings.security.newEmailPlaceholder')"
@update:model-value="emit('update:newEmail', String($event))" @update:model-value="emit('update:newEmail', String($event))"
/> />
<!-- <button <!-- <button
type="button" type="button"
class="verify-btn" class="verify-btn"
:class="{ verified: isEmailVerified }" :class="{ verified: isEmailVerified }"
@@ -31,212 +44,235 @@
> >
{{ 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" />
<div class="security-row"> <div class="security-row">
<div class="security-inline-row"> <div class="security-inline-row">
<div class="security-label inline">{{ t('Settings.security.password') }}</div> <div class="security-label inline">{{ t('Settings.security.password') }}</div>
<div class="security-static password-mask">.........</div> <div class="security-static password-mask">.........</div>
<button v-show="isEditing" type="button" class="small-btn" @click="emit('reset-password')"> <button
{{ t('Settings.buttons.cancel') }} v-show="isEditing && !isEditingPassword"
</button> type="button"
</div> class="small-btn"
</div> @click="emit('edit-password')"
>
{{ t('Settings.buttons.edit') }}
</button>
<button
v-show="isEditing && isEditingPassword"
type="button"
class="small-btn"
@click="emit('reset-password')"
>
{{ t('Settings.buttons.cancel') }}
</button>
</div>
</div>
<div v-show="isEditing" class="security-row"> <div v-show="isEditing && isEditingPassword" class="security-row">
<div class="security-label">{{ t('Settings.security.newPassword') }}</div> <div class="security-label">{{ t('Settings.security.newPassword') }}</div>
<div class="outlined-field"> <div class="outlined-field">
<el-input <el-input
:model-value="newPassword" :model-value="newPassword"
type="password" type="password"
show-password show-password
:placeholder="t('Settings.security.newPasswordPlaceholder')" :placeholder="t('Settings.security.newPasswordPlaceholder')"
@update:model-value="emit('update:newPassword', String($event))" @update:model-value="emit('update:newPassword', String($event))"
/> />
</div> </div>
<div class="security-tip">{{ t('Settings.security.passwordTip') }}</div> <div class="security-tip">{{ t('Settings.security.passwordTip') }}</div>
</div> </div>
<div v-show="isEditing" class="security-row"> <div v-show="isEditing && isEditingPassword" class="security-row">
<div class="security-label">{{ t('Settings.security.currentPassword') }}</div> <div class="security-label">{{ t('Settings.security.currentPassword') }}</div>
<div class="outlined-field"> <div class="outlined-field">
<el-input <el-input
:model-value="currentPassword" :model-value="currentPassword"
type="password" type="password"
show-password show-password
:placeholder="t('Settings.security.currentPasswordPlaceholder')" :placeholder="t('Settings.security.currentPasswordPlaceholder')"
@update:model-value="emit('update:currentPassword', String($event))" @update:model-value="emit('update:currentPassword', String($event))"
/> />
</div> </div>
</div> </div>
<div class="inner-divider" /> <div class="inner-divider" />
</SettingsSection> </SettingsSection>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import SettingsSection from './SettingsSection.vue' import SettingsSection from './SettingsSection.vue'
defineProps<{ defineProps<{
email: string email: string
newEmail: string newEmail: string
newPassword: string newPassword: string
currentPassword: string currentPassword: string
isEditing: boolean isEditing: boolean
isEmailVerified: boolean isEditingEmail: boolean
}>() isEditingPassword: boolean
isEmailVerified: boolean
}>()
const emit = defineEmits<{ const emit = defineEmits<{
(event: 'update:newEmail', value: string): void (event: 'update:newEmail', value: string): void
(event: 'update:newPassword', value: string): void (event: 'update:newPassword', value: string): void
(event: 'update:currentPassword', value: string): void (event: 'update:currentPassword', value: string): void
(event: 'reset-email'): void (event: 'edit-email'): void
(event: 'reset-password'): void (event: 'edit-password'): void
(event: 'verify-email'): void (event: 'reset-email'): void
}>() (event: 'reset-password'): void
(event: 'verify-email'): void
}>()
const { t } = useI18n() const { t } = useI18n()
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.field-text() { .field-text() {
font-family: 'KaiseiOpti-Regular'; font-family: 'KaiseiOpti-Regular';
font-size: 1.6rem; font-size: 1.6rem;
line-height: 2.4rem; line-height: 2.4rem;
color: #232323; color: #232323;
} }
.field-frame() { .field-frame() {
width: 100%; width: 100%;
min-height: 4rem; min-height: 4rem;
border: 0.1rem solid #979797; border: 0.1rem solid #979797;
} }
.control-wrapper() { .control-wrapper() {
box-shadow: none; box-shadow: none;
border-radius: 0; border-radius: 0;
padding: 0 2rem; padding: 0 2rem;
} }
.security-row + .security-row { .security-row + .security-row {
margin-top: 2.8rem; margin-top: 2.8rem;
} }
.security-label { .security-label {
margin: 0 0 0.8rem; margin: 0 0 0.8rem;
font-family: 'KaiseiOpti-Medium'; font-family: 'KaiseiOpti-Medium';
font-size: 1.4rem; font-size: 1.4rem;
line-height: 2.4rem; line-height: 2.4rem;
letter-spacing: 0.04em; letter-spacing: 0.04em;
color: #585858; color: #585858;
&.inline { &.inline {
width: 10.8rem; width: 10.8rem;
margin-bottom: 0; margin-bottom: 0;
flex-shrink: 0; flex-shrink: 0;
} }
} }
.security-static { .security-static {
.field-text(); .field-text();
display: flex; display: flex;
align-items: center; align-items: center;
flex: 1; flex: 1;
min-height: 2.4rem; min-height: 2.4rem;
padding: 0.1rem 0 0; padding: 0.1rem 0 0;
} }
.security-inline-row { .security-inline-row {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 2.8rem; gap: 2.8rem;
min-height: 3.2rem; min-height: 3.2rem;
} }
.security-tip { .security-tip {
margin-top: 0.6rem; margin-top: 0.6rem;
font-family: 'KaiseiOpti-Regular'; font-family: 'KaiseiOpti-Regular';
font-size: 1.2rem; font-size: 1.2rem;
line-height: 1.6rem; line-height: 1.6rem;
color: #9f9f9f; color: #9f9f9f;
} }
.outlined-field { .outlined-field {
.field-frame(); .field-frame();
:deep(.el-input) { :deep(.el-input) {
width: 100%; width: 100%;
min-height: 4rem; min-height: 4rem;
} }
:deep(.el-input__wrapper) { :deep(.el-input__wrapper) {
.control-wrapper(); .control-wrapper();
min-height: 4rem; min-height: 4rem;
} }
} }
.verify-field { .verify-field {
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: 0.8rem; margin-top: 0.8rem;
:deep(.el-input) { :deep(.el-input) {
flex: 1; flex: 1;
} }
} }
.verify-btn { .verify-btn {
border: none; border: none;
min-width: 11rem; min-width: 11rem;
height: 2.8rem; height: 2.8rem;
line-height: 2.8rem; line-height: 2.8rem;
border-left: 0.1rem solid #979797; border-left: 0.1rem solid #979797;
background: #ffffff; background: #ffffff;
font-family: 'KaiseiOpti-Medium'; font-family: 'KaiseiOpti-Medium';
font-size: 1.4rem; font-size: 1.4rem;
color: #232323; color: #232323;
cursor: pointer; cursor: pointer;
padding: 0 2rem; padding: 0 2rem;
&.verified { &.verified {
color: #ffffff; color: #ffffff;
background: #232323;
border-left-color: #232323;
}
}
.password-mask {
font-family: 'KaiseiOpti-Bold';
letter-spacing: 0.08rem;
}
.inner-divider {
height: 1px;
margin: 2rem 0;
background-color: #c4c4c4;
}
.small-btn {
width: 10rem;
height: 3.2rem;
align-self: flex-start;
border: 0.1rem solid #c4c4c4;
background: #f6f6f6;
font-family: 'KaiseiOpti-Bold';
font-size: 1.2rem;
line-height: 2.6rem;
letter-spacing: -0.03em;
color: #232323;
cursor: pointer;
/*
&.edit-btn {
border-color: #232323;
background: #232323; background: #232323;
border-left-color: #232323; color: #ffffff;
} } */
} }
.password-mask { .verified-tip {
font-family: 'KaiseiOpti-Bold'; color: #6f7f68;
letter-spacing: 0.08rem; }
}
.inner-divider {
height: 1px;
margin: 2rem 0;
background-color: #c4c4c4;
}
.small-btn {
width: 10rem;
height: 3.2rem;
align-self: flex-start;
border: 0.1rem solid #c4c4c4;
background: #f6f6f6;
font-family: 'KaiseiOpti-Bold';
font-size: 1.2rem;
line-height: 2.6rem;
letter-spacing: -0.03em;
color: #232323;
cursor: pointer;
}
.verified-tip {
color: #6f7f68;
}
</style> </style>

View File

@@ -25,7 +25,11 @@
v-model:current-password="securityDraft.currentPassword" v-model:current-password="securityDraft.currentPassword"
:email="displayData.email" :email="displayData.email"
:is-editing="isEditing" :is-editing="isEditing"
:is-editing-email="isEditingEmail"
:is-editing-password="isEditingPassword"
:is-email-verified="isEmailVerified" :is-email-verified="isEmailVerified"
@edit-email="handleEditEmail"
@edit-password="handleEditPassword"
@reset-email="resetSecurityEmail" @reset-email="resetSecurityEmail"
@reset-password="resetSecurityPassword" @reset-password="resetSecurityPassword"
@verify-email="handleVerifyEmail" @verify-email="handleVerifyEmail"
@@ -98,9 +102,13 @@
fullName, fullName,
roleModel, roleModel,
needsEmailVerification, needsEmailVerification,
isEditingEmail,
isEditingPassword,
handleEdit, handleEdit,
handleDiscard, handleDiscard,
handleSave, handleSave,
handleEditEmail,
handleEditPassword,
resetSecurityEmail, resetSecurityEmail,
resetSecurityPassword, resetSecurityPassword,
handleVerifyEmail, handleVerifyEmail,

View File

@@ -88,6 +88,8 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
const verificationTargetEmail = shallowRef('') const verificationTargetEmail = shallowRef('')
const verifiedEmail = shallowRef('') const verifiedEmail = shallowRef('')
const verificationCode = shallowRef('') const verificationCode = shallowRef('')
const isEditingEmail = shallowRef(false)
const isEditingPassword = shallowRef(false)
const roleList = computed(() => const roleList = computed(() =>
roleValues.map((value) => ({ roleValues.map((value) => ({
@@ -178,6 +180,8 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
draftData.value = cloneSettingsData(sourceData.value) draftData.value = cloneSettingsData(sourceData.value)
securityDraft.value = createEmptySecurityDraft() securityDraft.value = createEmptySecurityDraft()
resetEmailVerificationState() resetEmailVerificationState()
isEditingEmail.value = false
isEditingPassword.value = false
} }
const handleEdit = () => { const handleEdit = () => {
@@ -188,11 +192,21 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
const resetSecurityEmail = () => { const resetSecurityEmail = () => {
securityDraft.value.newEmail = '' securityDraft.value.newEmail = ''
resetEmailVerificationState() resetEmailVerificationState()
isEditingEmail.value = false
} }
const resetSecurityPassword = () => { const resetSecurityPassword = () => {
securityDraft.value.newPassword = '' securityDraft.value.newPassword = ''
securityDraft.value.currentPassword = '' securityDraft.value.currentPassword = ''
isEditingPassword.value = false
}
const handleEditEmail = () => {
isEditingEmail.value = true
}
const handleEditPassword = () => {
isEditingPassword.value = true
} }
const handleDiscard = () => { const handleDiscard = () => {
@@ -385,11 +399,15 @@ export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
fullName, fullName,
roleModel, roleModel,
needsEmailVerification, needsEmailVerification,
isEditingEmail,
isEditingPassword,
handleEdit, handleEdit,
handleDiscard, handleDiscard,
handleSave, handleSave,
resetSecurityEmail, resetSecurityEmail,
resetSecurityPassword, resetSecurityPassword,
handleEditEmail,
handleEditPassword,
handleVerifyEmail, handleVerifyEmail,
handleSendVerifyCode, handleSendVerifyCode,
handleVerificationSubmit, handleVerificationSubmit,