Files
zmAI/demo/frontend/src/views/ChangePassword.vue

385 lines
8.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="login-page">
<!-- Logo -->
<div class="logo">
<img src="/images/backgrounds/logo.svg?v=2" alt="Logo" />
</div>
<!-- 修改密码卡片 -->
<div class="login-card">
<!-- 标题 -->
<div class="page-title">{{ $t('changePassword.title') }}</div>
<!-- 表单 -->
<div class="password-form">
<!-- 当前密码可选 -->
<div class="input-group">
<a-input
v-model="form.currentPassword"
:placeholder="$t('changePassword.currentPasswordPlaceholder')"
class="password-input"
show-password
@keyup.enter="handleSubmit"
/>
<div class="input-error" v-if="errors.currentPassword">{{ errors.currentPassword }}</div>
</div>
<!-- 新密码 -->
<div class="input-group">
<a-input
v-model="form.newPassword"
:placeholder="$t('changePassword.newPasswordPlaceholder')"
class="password-input"
show-password
@keyup.enter="handleSubmit"
/>
<div class="input-error" v-if="errors.newPassword">{{ errors.newPassword }}</div>
</div>
<!-- 确认新密码 -->
<div class="input-group">
<a-input
v-model="form.confirmPassword"
:placeholder="$t('changePassword.confirmPasswordPlaceholder')"
class="password-input"
show-password
@keyup.enter="handleSubmit"
/>
<div class="input-error" v-if="errors.confirmPassword">{{ errors.confirmPassword }}</div>
</div>
<!-- 确定修改按钮 -->
<a-button
type="primary"
class="submit-button"
:loading="loading"
@click="handleSubmit"
>
{{ loading ? $t('changePassword.submitting') : $t('changePassword.confirm') }}
</a-button>
<!-- 返回按钮 -->
<div class="back-button-wrapper">
<a-button
class="back-button"
@click="handleBack"
>
{{ $t('common.back') }}
</a-button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { message } from 'ant-design-vue'
import request from '@/api/request'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const loading = ref(false)
// 判断是否首次设置密码
const isFirstTimeSetup = computed(() => {
return localStorage.getItem('needSetPassword') === '1'
})
const form = reactive({
currentPassword: '',
newPassword: '',
confirmPassword: ''
})
const errors = reactive({
currentPassword: '',
newPassword: '',
confirmPassword: ''
})
// 验证表单
const validateForm = () => {
let valid = true
errors.currentPassword = ''
errors.newPassword = ''
errors.confirmPassword = ''
// 当前密码为可选,不强制必填
// 新密码必填且必须包含英文字母和数字不少于8位
if (!form.newPassword) {
errors.newPassword = t('changePassword.enterNewPassword')
valid = false
} else if (form.newPassword.length < 8) {
errors.newPassword = t('changePassword.passwordMinLength')
valid = false
} else if (!/[a-zA-Z]/.test(form.newPassword)) {
errors.newPassword = t('changePassword.passwordNeedLetter')
valid = false
} else if (!/[0-9]/.test(form.newPassword)) {
errors.newPassword = t('changePassword.passwordNeedNumber')
valid = false
}
// 确认密码必填且必须与新密码一致
if (!form.confirmPassword) {
errors.confirmPassword = t('changePassword.confirmPasswordRequired')
valid = false
} else if (form.newPassword !== form.confirmPassword) {
errors.confirmPassword = t('changePassword.passwordMismatch')
valid = false
}
return valid
}
// 提交修改
const handleSubmit = async () => {
if (!validateForm()) return
loading.value = true
try {
const response = await request({
url: '/auth/change-password',
method: 'post',
data: {
oldPassword: form.currentPassword || null,
newPassword: form.newPassword
}
})
console.log('修改密码响应:', response)
// response.data 是后端返回的数据
const result = response.data
if (result && result.success) {
message.success(t('common.passwordSetSuccess'))
// 清除首次设置标记
localStorage.removeItem('needSetPassword')
// 跳转到首页或之前的页面
const redirect = route.query.redirect || '/profile'
router.replace(redirect)
} else {
message.error(result?.message || t('common.updateFailed'))
}
} catch (error) {
console.error('修改密码失败:', error)
const errorMsg = error.response?.data?.message || error.message || t('common.updateFailed')
message.error(errorMsg)
} finally {
loading.value = false
}
}
// 返回
const handleBack = () => {
if (isFirstTimeSetup.value) {
// 首次设置时返回到首页
router.replace('/')
} else {
// 非首次设置时返回上一页
router.back()
}
}
onMounted(() => {
// 检查用户是否已登录
if (!userStore.isAuthenticated) {
router.replace('/login')
}
})
</script>
<style scoped>
.login-page {
min-height: 100vh;
width: 100vw;
height: 100vh;
background: var(--bg-root) url('/images/backgrounds/login_bg.png') center/cover no-repeat;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
font-family: var(--font-sans);
margin: 0;
padding: 0;
z-index: var(--z-base);
}
/* 左上角Logo */
.logo {
position: absolute;
top: 30px;
left: 30px;
z-index: 10;
}
.logo img {
height: 40px;
width: auto;
}
/* 卡片 */
.login-card {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 550px;
max-width: 90vw;
background: var(--bg-glass);
backdrop-filter: blur(50px);
-webkit-backdrop-filter: blur(50px);
border-radius: var(--radius-2xl);
border: 1px solid var(--border-default);
padding: 60px 80px;
z-index: 10;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
/* 页面标题 */
.page-title {
text-align: center;
font-size: var(--text-4xl);
font-weight: var(--font-medium);
color: var(--text-primary);
margin-bottom: 50px;
}
/* 表单 */
.password-form {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
/* 输入组 */
.input-group {
margin-bottom: 5px;
}
.password-input {
width: 100%;
}
.password-input :deep(.ant-input) {
background: var(--bg-glass);
border: none;
border-radius: var(--radius-lg);
box-shadow: none;
height: 60px;
transition: all var(--duration-slow) var(--ease-default);
}
.password-input :deep(.ant-input:hover) {
background: var(--bg-glass-hover);
}
.password-input :deep(.ant-input:focus) {
background: var(--bg-active);
box-shadow: none;
}
.password-input :deep(.ant-input) {
color: var(--text-primary);
background: transparent;
font-size: var(--text-base);
}
.password-input :deep(.ant-input::placeholder) {
color: var(--text-tertiary);
}
.input-error {
color: var(--error-400);
font-size: var(--text-xs);
margin-top: var(--space-1);
text-align: left;
}
/* 确定修改按钮 */
.submit-button {
width: 100%;
height: 60px;
background: var(--primary-500);
border: none;
border-radius: var(--radius-lg);
color: var(--text-primary);
font-size: var(--text-lg);
font-weight: var(--font-medium);
margin-top: var(--space-5);
transition: all var(--duration-slow) var(--ease-default);
}
.submit-button:hover {
background: var(--primary-400);
transform: translateY(-1px);
}
.submit-button:active {
transform: translateY(0);
}
/* 返回按钮 */
.back-button {
width: 100%;
height: 60px;
background: var(--bg-glass);
border: none;
border-radius: var(--radius-lg);
color: var(--text-primary);
font-size: var(--text-lg);
font-weight: var(--font-medium);
transition: all var(--duration-slow) var(--ease-default);
}
.back-button:hover {
background: var(--bg-glass-hover);
}
.back-button-wrapper {
width: 100%;
}
.back-button-wrapper .back-button {
width: 100%;
}
/* 响应式设计 */
@media (max-width: 768px) {
.login-card {
width: 90%;
padding: var(--space-10) var(--space-8);
}
.page-title {
font-size: var(--text-3xl);
}
}
@media (max-width: 480px) {
.login-card {
padding: var(--space-8) var(--space-5);
}
.page-title {
font-size: var(--text-2xl);
}
}
</style>