重组前端功能目录并统一代码规范工具链 (#10)
Reviewed-on: #10
This commit was merged in pull request #10.
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { NAlert, NButton, NCard, NForm, NFormItem, NInput } from 'naive-ui'
|
||||
import { AuthClientError } from '@/features/auth/authClient'
|
||||
import { useAuthStore } from '@/features/auth/stores/auth'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const email = ref(typeof route.query.email === 'string' ? route.query.email : '')
|
||||
const password = ref('')
|
||||
const error = ref('')
|
||||
const loading = ref(false)
|
||||
const needsEmailConfirmation = ref(false)
|
||||
|
||||
function getSafeRedirect(value: unknown) {
|
||||
if (typeof value !== 'string' || !value.startsWith('/')) return '/'
|
||||
|
||||
try {
|
||||
const target = new URL(value, window.location.origin)
|
||||
return target.origin === window.location.origin
|
||||
? `${target.pathname}${target.search}${target.hash}`
|
||||
: '/'
|
||||
} catch {
|
||||
return '/'
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogin() {
|
||||
error.value = ''
|
||||
needsEmailConfirmation.value = false
|
||||
loading.value = true
|
||||
try {
|
||||
await authStore.login(email.value, password.value)
|
||||
void router.push(getSafeRedirect(route.query.redirect))
|
||||
} catch (e: unknown) {
|
||||
needsEmailConfirmation.value =
|
||||
e instanceof AuthClientError && e.code === 'EMAIL_NOT_VERIFIED'
|
||||
error.value = e instanceof Error ? e.message : '登录失败,请稍后重试'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-[calc(100vh-4rem)] px-4 py-10">
|
||||
<div
|
||||
class="mx-auto grid min-w-0 max-w-6xl gap-8 lg:grid-cols-[1.1fr_0.9fr] lg:items-stretch"
|
||||
>
|
||||
<section
|
||||
class="oc-panel-card-xl flex min-w-0 h-full flex-col justify-between overflow-hidden border border-slate-200 bg-[linear-gradient(135deg,#f2faf6_0%,#ffffff_48%,#eff6ff_100%)] p-6 sm:p-8"
|
||||
>
|
||||
<div>
|
||||
<p class="text-sm uppercase tracking-[0.26em] text-teal-700">Sky Log In</p>
|
||||
<h1
|
||||
class="mt-4 max-w-xl break-words text-4xl font-black leading-[1.05] text-slate-900 sm:text-5xl"
|
||||
>
|
||||
登录后继续记录
|
||||
<span class="block text-teal-700">你眼中的每一朵云</span>
|
||||
</h1>
|
||||
<!-- <p class="mt-6 max-w-lg text-base leading-8 text-slate-600">
|
||||
在地图、百科和画廊之间浏览观测记录。上传新的云图后,审核通过的作品会进入公开画廊与地图。
|
||||
</p> -->
|
||||
</div>
|
||||
<div class="mt-8 grid min-w-0 grid-cols-2 gap-4">
|
||||
<div class="min-w-0 border border-slate-200 bg-white px-4 py-3">
|
||||
<div class="oc-stat-label">Collection</div>
|
||||
<div class="oc-stat-value">10</div>
|
||||
<div class="oc-field-help">基础云属资料</div>
|
||||
</div>
|
||||
<div class="min-w-0 border border-slate-200 bg-white px-4 py-3">
|
||||
<div class="oc-stat-label">Mode</div>
|
||||
<div class="oc-stat-value">Atlas</div>
|
||||
<div class="oc-field-help">地图、百科、画廊一体</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<NCard class="oc-panel-card-xl min-w-0 h-full">
|
||||
<div class="mb-8">
|
||||
<div class="text-sm uppercase tracking-[0.22em] text-slate-500">
|
||||
Member Access
|
||||
</div>
|
||||
<h2 class="mt-3 text-3xl font-bold text-slate-900">登录 OpenCloud</h2>
|
||||
<p class="mt-2 text-sm text-slate-500">输入邮箱和密码,继续你的天空档案。</p>
|
||||
</div>
|
||||
|
||||
<NForm @submit.prevent="handleLogin">
|
||||
<NFormItem label="邮箱">
|
||||
<NInput
|
||||
v-model:value="email"
|
||||
type="text"
|
||||
:input-props="{
|
||||
autocomplete: 'username',
|
||||
inputmode: 'email',
|
||||
name: 'username',
|
||||
required: true,
|
||||
}"
|
||||
placeholder="your@email.com"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="密码">
|
||||
<NInput
|
||||
v-model:value="password"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
:input-props="{
|
||||
autocomplete: 'current-password',
|
||||
name: 'current-password',
|
||||
required: true,
|
||||
}"
|
||||
placeholder="输入密码"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NAlert v-if="error" type="error" class="mb-4">
|
||||
{{ error }}
|
||||
<div v-if="needsEmailConfirmation" class="mt-2">
|
||||
<RouterLink
|
||||
:to="{ path: '/resend-confirmation', query: { email } }"
|
||||
class="font-semibold text-rose-700 underline underline-offset-2"
|
||||
>
|
||||
重新发送验证邮件
|
||||
</RouterLink>
|
||||
</div>
|
||||
</NAlert>
|
||||
|
||||
<NButton
|
||||
attr-type="submit"
|
||||
type="primary"
|
||||
block
|
||||
size="large"
|
||||
class="oc-primary-button oc-primary-button--teal"
|
||||
:loading="loading"
|
||||
>
|
||||
{{ loading ? '登录中...' : '登录' }}
|
||||
</NButton>
|
||||
</NForm>
|
||||
|
||||
<div
|
||||
class="mt-6 flex flex-wrap items-center justify-between gap-3 text-sm text-slate-500"
|
||||
>
|
||||
<span>
|
||||
还没有账号?
|
||||
<RouterLink
|
||||
to="/register"
|
||||
class="font-semibold text-sky-700 hover:text-sky-800"
|
||||
>立即注册</RouterLink
|
||||
>
|
||||
</span>
|
||||
<RouterLink
|
||||
:to="{ path: '/forgot-password', query: { email } }"
|
||||
class="font-semibold text-teal-700 hover:text-teal-800"
|
||||
>
|
||||
忘记密码
|
||||
</RouterLink>
|
||||
</div>
|
||||
</NCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user