116 lines
3.7 KiB
Vue
116 lines
3.7 KiB
Vue
<script setup lang="ts">
|
||
import { ref } from 'vue'
|
||
import Icon from '../components/Icon.vue'
|
||
import { createUser, login, type Session } from '../db/auth'
|
||
|
||
const emit = defineEmits<{ (e: 'login', s: Session): void }>()
|
||
|
||
const mode = ref<'login' | 'create'>('login')
|
||
const username = ref('')
|
||
const password = ref('')
|
||
const password2 = ref('')
|
||
const error = ref('')
|
||
const busy = ref(false)
|
||
|
||
function switchMode(m: 'login' | 'create') {
|
||
mode.value = m
|
||
error.value = ''
|
||
password2.value = ''
|
||
}
|
||
|
||
async function submit() {
|
||
if (busy.value) return
|
||
error.value = ''
|
||
busy.value = true
|
||
try {
|
||
let s: Session
|
||
if (mode.value === 'login') {
|
||
s = await login(username.value, password.value)
|
||
} else {
|
||
if (password.value.length < 6) {
|
||
error.value = '登录密码至少 6 位'
|
||
busy.value = false
|
||
return
|
||
}
|
||
if (password.value !== password2.value) {
|
||
error.value = '两次输入的密码不一致'
|
||
busy.value = false
|
||
return
|
||
}
|
||
s = await createUser(username.value, password.value)
|
||
}
|
||
emit('login', s)
|
||
} catch (e) {
|
||
error.value = e instanceof Error ? e.message : '操作失败'
|
||
}
|
||
busy.value = false
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="login-page">
|
||
<div class="card login-card">
|
||
<div class="login-brand">
|
||
<div class="brand-icon"><Icon name="calendar" :size="22" /></div>
|
||
<div class="login-title">实习工作日志</div>
|
||
<div class="muted login-sub">打卡 · 记录 · 总结 · 备忘录 · 密码库</div>
|
||
<div class="muted login-sub">数据保存在云端服务器,所有设备登录同一账号即可实时同步</div>
|
||
</div>
|
||
<div class="seg">
|
||
<button class="seg-btn" :class="{ active: mode === 'login' }" @click="switchMode('login')">登录</button>
|
||
<button class="seg-btn" :class="{ active: mode === 'create' }" @click="switchMode('create')">创建用户</button>
|
||
</div>
|
||
<input class="input" v-model="username" placeholder="用户名(2 到 20 个字符)" @keydown.enter="submit" />
|
||
<input type="password" class="input" v-model="password" :placeholder="mode === 'login' ? '登录密码' : '设置登录密码(至少 6 位)'" @keydown.enter="submit" />
|
||
<input v-if="mode === 'create'" type="password" class="input" v-model="password2" placeholder="再次输入密码" @keydown.enter="submit" />
|
||
<div v-if="error" class="form-error">{{ error }}</div>
|
||
<button class="btn btn-primary login-btn" :disabled="busy" @click="submit">
|
||
{{ mode === 'login' ? '登录' : '创建并进入' }}
|
||
</button>
|
||
<div class="muted login-note">
|
||
登录密码仅服务器保存哈希;本机保存加密的自动登录凭据,登录后无需重复输入
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.login-page {
|
||
min-height: 100vh;
|
||
min-height: 100dvh;
|
||
background: var(--bg-app);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20px;
|
||
}
|
||
.login-card {
|
||
width: 400px;
|
||
max-width: 100%;
|
||
padding: 30px 28px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
box-shadow: var(--shadow-float);
|
||
border-radius: var(--radius-lg);
|
||
}
|
||
.login-brand { text-align: center; margin-bottom: 6px; }
|
||
.brand-icon {
|
||
width: 46px;
|
||
height: 46px;
|
||
margin: 0 auto 10px;
|
||
border-radius: 12px;
|
||
background: var(--accent);
|
||
color: #FFFFFF;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.login-title { font-size: 18px; font-weight: 600; color: var(--text-1); }
|
||
.login-sub { font-size: 12px; margin-top: 4px; line-height: 1.7; }
|
||
|
||
.form-error { color: var(--danger); font-size: 12px; }
|
||
.login-btn { margin-top: 2px; height: 36px; }
|
||
.login-note { font-size: 12px; text-align: center; line-height: 1.7; }
|
||
</style>
|