添加忘记密码

This commit is contained in:
2026-02-26 13:47:26 +08:00
parent 27b7ae69cc
commit cb02f9f32c
14 changed files with 288 additions and 65 deletions

View File

@@ -5,6 +5,8 @@ import request from '@/utils/request'
* 发送验证码 * 发送验证码
* @param params 发送验证码的参数 * @param params 发送验证码的参数
* @param params.email 邮箱 * @param params.email 邮箱
* @param params.type 验证码类型
* @param params.password 密码
* @returns 验证码发送成功的响应 * @returns 验证码发送成功的响应
*/ */
export const SendVerificationCode = (params) => { export const SendVerificationCode = (params) => {
@@ -36,7 +38,7 @@ export const Register = (data) => {
/** /**
* 登录用户 * 登录用户
* @param data 登录用户的参数 * @param data 登录用户的参数
* @param data.username 姓名 * @param data.email 邮箱
* @param data.password 密码 * @param data.password 密码
* @param data.verificationCode 验证码 * @param data.verificationCode 验证码
* @returns 登录成功后的token * @returns 登录成功后的token
@@ -49,3 +51,19 @@ export const Login = (data) => {
data data
}) })
} }
/**
* 忘记密码
* @param data 忘记密码的参数
* @param data.email 邮箱
* @param data.password 密码
* @param data.verificationCode 验证码
* @returns 忘记密码成功后的token
*/
export const ForgetPassword = (data) => {
return request({
url: '/api/user/forget-password',
method: 'post',
loading: true,
data
})
}

View File

@@ -9,6 +9,9 @@
transform: `scale(${data.scale})` transform: `scale(${data.scale})`
}" }"
> >
<result-image />
<card type="cards-select" /> <card type="cards-select" />
<card type="to-real-style" /> <card type="to-real-style" />

View File

@@ -1,7 +1,7 @@
<template> <template>
<!-- 高级工具选择 --> <!-- 高级工具选择 -->
<div class="cards-select"> <div class="cards-select">
<div v-for="v in list" :key="v.type"> <div v-for="v in list" :key="v.type" @click="onClickItem(v)">
<span class="icon"> <span class="icon">
<svg-icon :name="v.type + '-2'" size="15" size-unit="px" /> <svg-icon :name="v.type + '-2'" size="15" size-unit="px" />
</span> </span>
@@ -38,16 +38,21 @@
title: 'To 3D Model' title: 'To 3D Model'
} }
]) ])
const onClickItem = (v) => {
console.log(v.type)
}
defineExpose({}) defineExpose({})
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.cards-select { .cards-select {
--cards-item-margin-bottom: 8px;
user-select: none;
> div { > div {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 6px 5px; padding: 0 5px;
height: 30px;
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: pointer;
&:hover { &:hover {

View File

@@ -170,7 +170,7 @@
&:deep(> *) { &:deep(> *) {
width: 100%; width: 100%;
> * { > * {
margin-bottom: 10px; margin-bottom: var(--cards-item-margin-bottom, 10px);
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }

View File

@@ -0,0 +1,33 @@
<template>
<!-- 转3-View -->
<div class="to-3view">
<p class="label">3D Model</p>
<div class="image">
<img
src="https://s3-alpha-sig.figma.com/img/ea2f/590e/9638f62a2fc91e31f33db0022db1642c?Expires=1773014400&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=M0B8oJJOk~dGG0aZAqOIocAp7T0LFdJ9FYmCrEZVTCRzYxM6SJRNtYMTX-rTO3Z~s14QINh~o-S41XiZnBv-0zcKjuWot~VVaNHfd0~1LesfNe2KwvCinT~72btFut1pheLnKE-wWCX5ewtonxU77bnw386YPMTqv7DBZzksf2udsJA7NmOYD6~TUG3Q2dWSt~zPH~lkaidscPqpCnCbqzljCEi4RiHY4U3A45l5XypcX2umqn1UaYUFCTqV9471J4qdB6Dg2pcKocdp-7-3s1De6Q~2SmBOrSgDQ~KEADCB2lhKfhxgWmy0lwMvhTd4l90ygVZDWZRABgjHNrGUvg__"
alt=""
/>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, onMounted } from 'vue'
const data = reactive({})
defineExpose({ data })
</script>
<style lang="less" scoped>
.to-3view {
> .image {
padding: 18px;
border-radius: 10px;
background-color: #f0f0f0;
> img {
width: 100%;
height: auto;
}
}
}
</style>

View File

@@ -9,62 +9,62 @@ export class StateManager {
constructor(options) { constructor(options) {
this.vueFlow = options.vueFlow this.vueFlow = options.vueFlow
this.nodes = ref<any[]>([ this.nodes = ref<any[]>([
{ // {
id: '1', // id: '1',
type: 'InputNode', // type: 'InputNode',
class: 'custom-node start', // class: 'custom-node start',
position: { x: 0, y: 0 }, // position: { x: 0, y: 0 },
data: { component: card, type: 'to-real-style' } // data: { component: card, type: 'to-real-style' }
}, // },
{ // {
id: '2', // id: '2',
type: 'SecondaryNode', // type: 'SecondaryNode',
class: 'custom-node', // class: 'custom-node',
position: { x: 0, y: 0 }, // position: { x: 0, y: 0 },
data: { component: card, type: 'scene-composition' } // data: { component: card, type: 'scene-composition' }
}, // },
{ // {
id: '3', // id: '3',
type: 'SecondaryNode', // type: 'SecondaryNode',
class: 'custom-node', // class: 'custom-node',
position: { x: 0, y: 0 }, // position: { x: 0, y: 0 },
data: { component: card, type: 'color-palette' } // data: { component: card, type: 'color-palette' }
}, // },
{ // {
id: '4', // id: '4',
type: 'SecondaryNode', // type: 'SecondaryNode',
class: 'custom-node', // class: 'custom-node',
position: { x: 0, y: 0 }, // position: { x: 0, y: 0 },
data: { component: card, type: 'to-video' } // data: { component: card, type: 'to-video' }
}, // },
{ // {
id: '5', // id: '5',
type: 'SecondaryNode', // type: 'SecondaryNode',
class: 'custom-node', // class: 'custom-node',
position: { x: 0, y: 0 }, // position: { x: 0, y: 0 },
data: { component: card, type: 'to-3d-model' } // data: { component: card, type: 'to-3d-model' }
}, // },
{ // {
id: '6', // id: '6',
type: 'SecondaryNode', // type: 'SecondaryNode',
class: 'custom-node', // class: 'custom-node',
position: { x: 0, y: 0 }, // position: { x: 0, y: 0 },
data: { component: card, type: 'to-cad' } // data: { component: card, type: 'to-cad' }
}, // },
{ // {
id: '7', // id: '7',
type: 'SecondaryNode', // type: 'SecondaryNode',
class: 'custom-node', // class: 'custom-node',
position: { x: 0, y: 0 }, // position: { x: 0, y: 0 },
data: { component: card, type: 'add-print' } // data: { component: card, type: 'add-print' }
}, // },
{ // {
id: '8', // id: '8',
type: 'SecondaryNode', // type: 'SecondaryNode',
class: 'custom-node', // class: 'custom-node',
position: { x: 0, y: 0 }, // position: { x: 0, y: 0 },
data: { component: card, type: 'edit-material' } // data: { component: card, type: 'edit-material' }
} // }
]); ]);
this.edges = computed(() => { this.edges = computed(() => {

View File

@@ -1,5 +1,6 @@
export default { export default {
AlphaVersion: '2026 Alpha Version', AlphaVersion: '2026 Alpha Version',
submit: 'Submit',
Login: { Login: {
login: 'Log in', login: 'Log in',
register: 'Register', register: 'Register',
@@ -35,6 +36,7 @@ export default {
wechatLogin: 'Sign in with Wechat', wechatLogin: 'Sign in with Wechat',
indexTip: 'A multi-agent canvas for rapid, trend driven design iteration.', indexTip: 'A multi-agent canvas for rapid, trend driven design iteration.',
sendCodeError: 'Send code error', sendCodeError: 'Send code error',
retrievePassword: 'Retrieve password',
}, },
Nuic: { Nuic: {
hiName: 'Hi, {name}. This is Fiphant.', hiName: 'Hi, {name}. This is Fiphant.',

View File

@@ -1,5 +1,6 @@
export default { export default {
AlphaVersion: '2026版本', AlphaVersion: '2026版本',
submit: '提交',
Login: { Login: {
login: '登录', login: '登录',
register: '注册', register: '注册',
@@ -36,6 +37,7 @@ export default {
wechatLogin: '使用微信登录', wechatLogin: '使用微信登录',
indexTip: '一个多智能体画布,用于快速、趋势驱动的设计迭代。', indexTip: '一个多智能体画布,用于快速、趋势驱动的设计迭代。',
sendCodeError: '发送验证码失败', sendCodeError: '发送验证码失败',
retrievePassword: '找回密码',
}, },
Nuic: { Nuic: {
hiName: '你好,{name}。这是 Fiphant。', hiName: '你好,{name}。这是 Fiphant。',

View File

@@ -29,6 +29,11 @@ const router = createRouter({
name: 'register', name: 'register',
component: () => import('../views/login/register.vue') component: () => import('../views/login/register.vue')
}, },
{
path: '/retrievepass',
name: 'retrievepass',
component: () => import('../views/login/retrieve-password.vue')
},
{ {
path: '/nuic', path: '/nuic',
name: 'nuic', name: 'nuic',

View File

@@ -1,3 +1,4 @@
.retrieve-password,
.register, .register,
.login { .login {
width: 100%; width: 100%;
@@ -6,6 +7,7 @@
display: flex; display: flex;
user-select: none; user-select: none;
} }
.retrieve-password > .left,
.register > .left, .register > .left,
.login > .left { .login > .left {
flex: 1; flex: 1;
@@ -13,12 +15,14 @@
position: relative; position: relative;
overflow: hidden; overflow: hidden;
} }
.retrieve-password > .left > .bg,
.register > .left > .bg, .register > .left > .bg,
.login > .left > .bg { .login > .left > .bg {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
.retrieve-password > .left > .logo,
.register > .left > .logo, .register > .left > .logo,
.login > .left > .logo { .login > .left > .logo {
position: absolute; position: absolute;
@@ -27,12 +31,14 @@
width: auto; width: auto;
height: 2.5rem; height: 2.5rem;
} }
.retrieve-password > .left > .logo > span,
.register > .left > .logo > span, .register > .left > .logo > span,
.login > .left > .logo > span { .login > .left > .logo > span {
font-weight: 600; font-weight: 600;
font-size: 3.3rem; font-size: 3.3rem;
font-family: SemiBold; font-family: SemiBold;
} }
.retrieve-password > .right,
.register > .right, .register > .right,
.login > .right { .login > .right {
width: 99rem; width: 99rem;
@@ -44,11 +50,13 @@
background-position: left center; background-position: left center;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.retrieve-password > .right > .top,
.register > .right > .top, .register > .right > .top,
.login > .right > .top { .login > .right > .top {
display: flex; display: flex;
padding: 3rem 0 0 3rem; padding: 3rem 0 0 3rem;
} }
.retrieve-password > .right > .top > .back,
.register > .right > .top > .back, .register > .right > .top > .back,
.login > .right > .top > .back { .login > .right > .top > .back {
width: 5rem; width: 5rem;
@@ -58,6 +66,7 @@
background-color: transparent; background-color: transparent;
cursor: pointer; cursor: pointer;
} }
.retrieve-password > .right > .box,
.register > .right > .box, .register > .right > .box,
.login > .right > .box { .login > .right > .box {
min-width: 50rem; min-width: 50rem;
@@ -68,17 +77,20 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
.retrieve-password > .right > .box > img,
.register > .right > .box > img, .register > .right > .box > img,
.login > .right > .box > img { .login > .right > .box > img {
width: auto; width: auto;
height: 12rem; height: 12rem;
margin-top: 2rem; margin-top: 2rem;
} }
.retrieve-password > .right > .box > .visible-code,
.register > .right > .box > .visible-code, .register > .right > .box > .visible-code,
.login > .right > .box > .visible-code { .login > .right > .box > .visible-code {
margin-top: 1.7rem; margin-top: 1.7rem;
margin-bottom: 7.2rem; margin-bottom: 7.2rem;
} }
.retrieve-password > .right > .box > .title,
.register > .right > .box > .title, .register > .right > .box > .title,
.login > .right > .box > .title { .login > .right > .box > .title {
font-weight: 600; font-weight: 600;
@@ -88,12 +100,14 @@
justify-content: center; justify-content: center;
color: #252727; color: #252727;
} }
.retrieve-password > .right > .box > .title:deep(*),
.register > .right > .box > .title:deep(*), .register > .right > .box > .title:deep(*),
.login > .right > .box > .title:deep(*) { .login > .right > .box > .title:deep(*) {
margin-left: 1rem; margin-left: 1rem;
font-family: LBold; font-family: LBold;
margin-bottom: -1rem; margin-bottom: -1rem;
} }
.retrieve-password > .right > .box > .tip,
.register > .right > .box > .tip, .register > .right > .box > .tip,
.login > .right > .box > .tip { .login > .right > .box > .tip {
font-weight: 400; font-weight: 400;
@@ -102,16 +116,19 @@
color: #666; color: #666;
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.retrieve-password > .right > .box:deep(.el-form),
.register > .right > .box:deep(.el-form), .register > .right > .box:deep(.el-form),
.login > .right > .box:deep(.el-form) { .login > .right > .box:deep(.el-form) {
margin-top: 5rem; margin-top: 5rem;
width: 100%; width: 100%;
font-family: Regular; font-family: Regular;
} }
.retrieve-password > .right > .box:deep(.el-form) .el-form-item,
.register > .right > .box:deep(.el-form) .el-form-item, .register > .right > .box:deep(.el-form) .el-form-item,
.login > .right > .box:deep(.el-form) .el-form-item { .login > .right > .box:deep(.el-form) .el-form-item {
margin-bottom: 2rem; margin-bottom: 2rem;
} }
.retrieve-password > .right > .box:deep(.el-form) .el-form-item__label,
.register > .right > .box:deep(.el-form) .el-form-item__label, .register > .right > .box:deep(.el-form) .el-form-item__label,
.login > .right > .box:deep(.el-form) .el-form-item__label { .login > .right > .box:deep(.el-form) .el-form-item__label {
color: #252727; color: #252727;
@@ -119,6 +136,7 @@
margin-bottom: 0.8rem; margin-bottom: 0.8rem;
font-family: Medium; font-family: Medium;
} }
.retrieve-password > .right > .box:deep(.el-form) .el-input,
.register > .right > .box:deep(.el-form) .el-input, .register > .right > .box:deep(.el-form) .el-input,
.login > .right > .box:deep(.el-form) .el-input { .login > .right > .box:deep(.el-form) .el-input {
--el-input-height: 5rem; --el-input-height: 5rem;
@@ -127,6 +145,7 @@
--el-border-color: #dfdfdf; --el-border-color: #dfdfdf;
font-size: 1.4rem; font-size: 1.4rem;
} }
.retrieve-password > .right > .box:deep(.el-form) .forgetPassword,
.register > .right > .box:deep(.el-form) .forgetPassword, .register > .right > .box:deep(.el-form) .forgetPassword,
.login > .right > .box:deep(.el-form) .forgetPassword { .login > .right > .box:deep(.el-form) .forgetPassword {
margin-top: -1.2rem; margin-top: -1.2rem;
@@ -137,27 +156,32 @@
cursor: pointer; cursor: pointer;
text-decoration: underline; text-decoration: underline;
} }
.retrieve-password > .right > .box:deep(.el-form) .privacy,
.register > .right > .box:deep(.el-form) .privacy, .register > .right > .box:deep(.el-form) .privacy,
.login > .right > .box:deep(.el-form) .privacy { .login > .right > .box:deep(.el-form) .privacy {
--el-checkbox-height: auto; --el-checkbox-height: auto;
margin-bottom: 4rem; margin-bottom: 4rem;
} }
.retrieve-password > .right > .box:deep(.el-form) .privacy .el-checkbox__label,
.register > .right > .box:deep(.el-form) .privacy .el-checkbox__label, .register > .right > .box:deep(.el-form) .privacy .el-checkbox__label,
.login > .right > .box:deep(.el-form) .privacy .el-checkbox__label { .login > .right > .box:deep(.el-form) .privacy .el-checkbox__label {
font-size: 1.6rem; font-size: 1.6rem;
color: #666666; color: #666666;
font-weight: 400; font-weight: 400;
} }
.retrieve-password > .right > .box:deep(.el-form) .privacy .el-checkbox__label > div > span,
.register > .right > .box:deep(.el-form) .privacy .el-checkbox__label > div > span, .register > .right > .box:deep(.el-form) .privacy .el-checkbox__label > div > span,
.login > .right > .box:deep(.el-form) .privacy .el-checkbox__label > div > span { .login > .right > .box:deep(.el-form) .privacy .el-checkbox__label > div > span {
text-decoration: underline; text-decoration: underline;
cursor: pointer; cursor: pointer;
} }
.retrieve-password > .right > .box:deep(.el-form) .el-form-item__error,
.register > .right > .box:deep(.el-form) .el-form-item__error, .register > .right > .box:deep(.el-form) .el-form-item__error,
.login > .right > .box:deep(.el-form) .el-form-item__error { .login > .right > .box:deep(.el-form) .el-form-item__error {
padding-top: 1px; padding-top: 1px;
font-size: 1.4rem; font-size: 1.4rem;
} }
.retrieve-password > .right > .box:deep(.el-form) .submit,
.register > .right > .box:deep(.el-form) .submit, .register > .right > .box:deep(.el-form) .submit,
.login > .right > .box:deep(.el-form) .submit { .login > .right > .box:deep(.el-form) .submit {
width: 100%; width: 100%;
@@ -169,6 +193,7 @@
font-weight: 600; font-weight: 600;
font-family: SemiBold; font-family: SemiBold;
} }
.retrieve-password > .right > .box > .tip-2,
.register > .right > .box > .tip-2, .register > .right > .box > .tip-2,
.login > .right > .box > .tip-2 { .login > .right > .box > .tip-2 {
font-weight: 400; font-weight: 400;
@@ -176,12 +201,14 @@
color: #666; color: #666;
font-family: Regular; font-family: Regular;
} }
.retrieve-password > .right > .box > .tip-2:deep(span),
.register > .right > .box > .tip-2:deep(span), .register > .right > .box > .tip-2:deep(span),
.login > .right > .box > .tip-2:deep(span) { .login > .right > .box > .tip-2:deep(span) {
text-decoration: underline; text-decoration: underline;
color: #FF7A50; color: #FF7A50;
cursor: pointer; cursor: pointer;
} }
.retrieve-password > .right > .box > .other-login,
.register > .right > .box > .other-login, .register > .right > .box > .other-login,
.login > .right > .box > .other-login { .login > .right > .box > .other-login {
margin-top: 5rem; margin-top: 5rem;

View File

@@ -1,3 +1,4 @@
.retrieve-password,
.register, .register,
.login { .login {
width: 100%; width: 100%;

View File

@@ -33,7 +33,7 @@
/> />
</el-form-item> </el-form-item>
<div class="forgetPassword"> <div class="forgetPassword">
<span>{{ $t('Login.forgetPassword') }}</span> <span @click="onForgetPassword">{{ $t('Login.forgetPassword') }}</span>
</div> </div>
<el-form-item prop="privacy" class="privacy"> <el-form-item prop="privacy" class="privacy">
<el-checkbox v-model="formData.privacy"> <el-checkbox v-model="formData.privacy">
@@ -92,6 +92,9 @@
router.back() router.back()
} }
} }
const onForgetPassword = () => {
router.push({ name: 'retrievepass' })
}
const visibleCodeRef = ref(null) const visibleCodeRef = ref(null)
const onSubmit = () => { const onSubmit = () => {
formRef.value?.validate?.((valid) => { formRef.value?.validate?.((valid) => {

View File

@@ -0,0 +1,121 @@
<template>
<div class="retrieve-password">
<div class="left">
<img class="bg" src="@/assets/images/login/left-bg.png" />
<img class="logo" src="@/assets/images/logo-1.png" />
</div>
<div class="right">
<div class="top">
<button class="back" @click="onBack">
<svg-icon name="arrow-left" size="37" />
</button>
</div>
<div class="box">
<img src="@/assets/images/login/elephant.png" />
<template v-if="!isVisible">
<div class="title">{{ $t('Login.retrievePassword') }}</div>
<el-form :model="formData" :rules="ruleForm" label-position="top" ref="formRef">
<el-form-item :label="$t('Login.email')" prop="email">
<el-input
v-model="formData.email"
:placeholder="$t('Login.enterEmail')"
name="email"
/>
</el-form-item>
<el-form-item :label="$t('Login.password')" prop="password">
<el-input
v-model="formData.password"
:placeholder="$t('Login.enterPassword')"
type="password"
show-password
name="password"
/>
</el-form-item>
<br />
<br />
<el-form-item>
<el-button class="submit" type="primary" @click="onSubmit">{{
$t('submit')
}}</el-button>
</el-form-item>
</el-form>
</template>
<visible-code
v-show="isVisible"
type="FORGOT_PWD"
ref="visibleCodeRef"
:email="formData.email"
@submit="onVerifyCode"
/>
<other-login />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import md5 from 'md5'
import { ForgetPassword } from '@/api/login'
import { computed, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { validateEmail, validatePass } from './tools'
import OtherLogin from './other-login.vue'
import VisibleCode from './visible-code.vue'
import { useUserInfoStore } from '@/stores'
const userInfoStore = useUserInfoStore()
const router = useRouter()
const ruleForm = reactive({
email: [{ validator: validateEmail, trigger: 'blur' }],
password: [{ validator: validatePass, trigger: 'blur' }]
})
const isVisible = ref(false)
const formData = reactive({
email: '',
password: ''
})
const formRef = ref(null)
const onBack = () => {
if (isVisible.value) {
isVisible.value = false
} else {
router.back()
}
}
const visibleCodeRef = ref(null)
const onSubmit = () => {
formRef.value?.validate?.((valid) => {
if (valid) {
// console.log('submit!')
visibleCodeRef.value?.onSendCode().then(() => {
isVisible.value = true
})
} else {
console.warn('error submit!')
}
})
}
const onVerifyCode = (code: string) => {
// console.log(code)
ForgetPassword({
email: formData.email,
password: md5(formData.password),
verificationCode: code
})
.then((res) => {
if (res) {
// userInfoStore.setToken(res)
// userInfoStore.setUserInfo({
// email: formData.email
// })
// router.push({ name: 'mainInput' })
}
})
.catch((error) => {
console.warn(error)
})
}
</script>
<style lang="less" scoped>
@import './less/style.less';
</style>

View File

@@ -25,7 +25,10 @@
const emit = defineEmits(['submit']) const emit = defineEmits(['submit'])
const props = defineProps({ const props = defineProps({
email: { type: String, required: true }, email: { type: String, required: true },
type: { default: 'LOGIN', type: String as () => 'LOGIN' | 'REGISTER' }, type: {
type: String as () => 'LOGIN' | 'REGISTER' | 'FORGOT_PWD',
required: true
},
password: { type: String, default: '' } password: { type: String, default: '' }
}) })
const code = ref('') const code = ref('')