feat: email register and login

This commit is contained in:
tim
2025-07-05 02:06:46 +08:00
parent d540c995a9
commit 3568e54984
4 changed files with 71 additions and 21 deletions
+1
View File
@@ -1,6 +1,7 @@
:root { :root {
--primary-color-hover: rgb(9, 95, 105); --primary-color-hover: rgb(9, 95, 105);
--primary-color: rgb(10, 110, 120); --primary-color: rgb(10, 110, 120);
--primary-color-disabled: rgba(93, 152, 156, 0.5);
--header-height: 60px; --header-height: 60px;
--header-background-color: white; --header-background-color: white;
--header-border-color: lightgray; --header-border-color: lightgray;
+4 -4
View File
@@ -4,13 +4,13 @@ import router from './router'
import './assets/global.css' import './assets/global.css'
import Toast, { POSITION } from 'vue-toastification' import Toast, { POSITION } from 'vue-toastification'
import 'vue-toastification/dist/index.css' import 'vue-toastification/dist/index.css'
import { useToast } from 'vue-toastification'
// Configurable API domain and port // Configurable API domain and port
export const API_DOMAIN = export const API_DOMAIN = 'http://127.0.0.1'
process.env.VUE_APP_API_DOMAIN || export const API_PORT = 8081
`${window.location.protocol}//${window.location.hostname}`
export const API_PORT = process.env.VUE_APP_API_PORT || window.location.port
export const API_BASE_URL = API_PORT ? `${API_DOMAIN}:${API_PORT}` : API_DOMAIN export const API_BASE_URL = API_PORT ? `${API_DOMAIN}:${API_PORT}` : API_DOMAIN
export const toast = useToast()
const app = createApp(App) const app = createApp(App)
app.use(router) app.use(router)
+26 -6
View File
@@ -29,10 +29,17 @@
</div> </div>
<div class="login-page-button-primary" @click="submitLogin"> <div v-if="!isWaitingForLogin" class="login-page-button-primary" @click="submitLogin">
<div class="login-page-button-text">登录</div> <div class="login-page-button-text">登录</div>
</div> </div>
<div v-else class="login-page-button-primary disabled">
<div class="login-page-button-text">
<i class="fas fa-spinner fa-spin"></i>
登录中...
</div>
</div>
<div class="login-page-button-secondary">没有账号 <a class="login-page-button-secondary-link" href="/signup">注册</a> <div class="login-page-button-secondary">没有账号 <a class="login-page-button-secondary-link" href="/signup">注册</a>
</div> </div>
</div> </div>
@@ -48,33 +55,36 @@
</template> </template>
<script> <script>
import { API_BASE_URL } from '../main' import { API_BASE_URL, toast } from '../main'
export default { export default {
name: 'LoginPageView', name: 'LoginPageView',
data() { data() {
return { return {
username: '', username: '',
password: '' password: '',
isWaitingForLogin: false
} }
}, },
methods: { methods: {
async submitLogin() { async submitLogin() {
try { try {
this.isWaitingForLogin = true
const res = await fetch(`${API_BASE_URL}/api/auth/login`, { const res = await fetch(`${API_BASE_URL}/api/auth/login`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: this.username, password: this.password }) body: JSON.stringify({ username: this.username, password: this.password })
}) })
this.isWaitingForLogin = false
const data = await res.json() const data = await res.json()
if (res.ok && data.token) { if (res.ok && data.token) {
localStorage.setItem('token', data.token) localStorage.setItem('token', data.token)
this.$toast.success('登录成功') toast.success('登录成功')
this.$router.push('/') this.$router.push('/')
} else { } else {
this.$toast.error(data.error || '登录失败') toast.error(data.error || '登录失败')
} }
} catch (e) { } catch (e) {
this.$toast.error('登录失败') toast.error('登录失败')
} }
} }
} }
@@ -178,6 +188,16 @@ export default {
background-color: var(--primary-color-hover); background-color: var(--primary-color-hover);
} }
.login-page-button-primary.disabled {
background-color: var(--primary-color-disabled);
opacity: 0.5;
cursor: not-allowed;
}
.login-page-button-primary.disabled:hover {
background-color: var(--primary-color-disabled);
}
.login-page-button { .login-page-button {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
+40 -11
View File
@@ -54,9 +54,15 @@
> >
</div> </div>
<div class="signup-page-button-primary" @click="sendVerification"> <div v-if="!isWaitingForEmailSent" class="signup-page-button-primary" @click="sendVerification">
<div class="signup-page-button-text">验证邮箱</div> <div class="signup-page-button-text">验证邮箱</div>
</div> </div>
<div v-else class="signup-page-button-primary disabled">
<div class="signup-page-button-text">
<i class="fas fa-spinner fa-spin"></i>
发送中...
</div>
</div>
<div class="signup-page-button-secondary">已经有账号 <a class="signup-page-button-secondary-link" <div class="signup-page-button-secondary">已经有账号 <a class="signup-page-button-secondary-link"
href="/login">登录</a></div> href="/login">登录</a></div>
@@ -72,9 +78,15 @@
placeholder="邮箱验证码" placeholder="邮箱验证码"
> >
</div> </div>
<div class="signup-page-button-primary" @click="verifyCode"> <div v-if="!isWaitingForEmailVerified" class="signup-page-button-primary" @click="verifyCode">
<div class="signup-page-button-text">注册</div> <div class="signup-page-button-text">注册</div>
</div> </div>
<div v-else class="signup-page-button-primary disabled">
<div class="signup-page-button-text">
<i class="fas fa-spinner fa-spin"></i>
验证中...
</div>
</div>
</div> </div>
</div> </div>
@@ -88,7 +100,7 @@
</template> </template>
<script> <script>
import { API_BASE_URL } from '../main' import { API_BASE_URL, toast } from '../main'
export default { export default {
name: 'SignupPageView', name: 'SignupPageView',
@@ -102,7 +114,9 @@ export default {
usernameError: '', usernameError: '',
passwordError: '', passwordError: '',
nickname: '', nickname: '',
code: '' code: '',
isWaitingForEmailSent: false,
isWaitingForEmailVerified: false
} }
}, },
methods: { methods: {
@@ -127,46 +141,51 @@ export default {
return return
} }
try { try {
console.log('base url: ', API_BASE_URL)
this.isWaitingForEmailSent = true
const res = await fetch(`${API_BASE_URL}/api/auth/register`, { const res = await fetch(`${API_BASE_URL}/api/auth/register`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
username: this.username, username: this.username,
email: this.email, email: this.email,
password: this.password password: this.password,
}) })
}) })
this.isWaitingForEmailSent = false
const data = await res.json() const data = await res.json()
if (res.ok) { if (res.ok) {
this.emailStep = 1 this.emailStep = 1
this.$toast.success('验证码已发送,请查看邮箱') toast.success('验证码已发送,请查看邮箱')
} else if (data.field) { } else if (data.field) {
if (data.field === 'username') this.usernameError = data.error if (data.field === 'username') this.usernameError = data.error
if (data.field === 'email') this.emailError = data.error if (data.field === 'email') this.emailError = data.error
if (data.field === 'password') this.passwordError = data.error if (data.field === 'password') this.passwordError = data.error
} else { } else {
this.$toast.error(data.error || '发送失败') toast.error(data.error || '发送失败')
} }
} catch (e) { } catch (e) {
this.$toast.error('发送失败') toast.error('发送失败')
} }
}, },
async verifyCode() { async verifyCode() {
try { try {
this.isWaitingForEmailVerified = true
const res = await fetch(`${API_BASE_URL}/api/auth/verify`, { const res = await fetch(`${API_BASE_URL}/api/auth/verify`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: this.username, code: this.code }) body: JSON.stringify({ username: this.username, code: this.code })
}) })
this.isWaitingForEmailVerified = false
const data = await res.json() const data = await res.json()
if (res.ok) { if (res.ok) {
this.$toast.success('注册成功,请登录') toast.success('注册成功,请登录')
this.$router.push('/login') this.$router.push('/login')
} else { } else {
this.$toast.error(data.error || '注册失败') toast.error(data.error || '注册失败')
} }
} catch (e) { } catch (e) {
this.$toast.error('注册失败') toast.error('注册失败')
} }
} }
} }
@@ -266,6 +285,16 @@ export default {
gap: 10px; gap: 10px;
} }
.signup-page-button-primary.disabled {
background-color: var(--primary-color-disabled);
opacity: 0.5;
cursor: not-allowed;
}
.signup-page-button-primary.disabled:hover {
background-color: var(--primary-color-disabled);
}
.signup-page-button-primary:hover { .signup-page-button-primary:hover {
background-color: var(--primary-color-hover); background-color: var(--primary-color-hover);
} }