feat: support nickname login and nickname uniqueness check v0.0.3

- Login: query user by name field, authenticate with username
- Register: add blur-triggered nickname uniqueness validation
- Requires PocketBase users collection listRule/viewRule set to public

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
congsh
2026-04-18 13:35:41 +08:00
parent 0a7dcbb6b8
commit 3173525a2e
2 changed files with 48 additions and 8 deletions
+20 -7
View File
@@ -4,6 +4,7 @@ import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
import { pb } from '@/api/pocketbase'
import PasswordInput from '@/components/common/PasswordInput.vue'
const router = useRouter()
@@ -15,16 +16,29 @@ const loading = ref(false)
async function handleLogin() {
if (!identity.value || !password.value) {
ElMessage.warning('请输入邮箱和密码')
ElMessage.warning('请输入昵称/邮箱和密码')
return
}
try {
loading.value = true
await userStore.login(identity.value, password.value)
let loginIdentity = identity.value.trim()
const redirect = '/'
router.push(redirect)
// 如果不包含 @,按昵称或用户名查找对应 username 用于认证
if (!loginIdentity.includes('@')) {
const result = await pb.collection('users').getList(1, 1, {
filter: `name="${loginIdentity}" || username="${loginIdentity}"`,
$autoCancel: false
})
if (result.items.length === 0) {
ElMessage.error('用户不存在')
return
}
loginIdentity = (result.items[0] as any).username
}
await userStore.login(loginIdentity, password.value)
router.push('/')
} catch (error: any) {
ElMessage.error(error.message || '登录失败')
} finally {
@@ -45,12 +59,11 @@ async function handleLogin() {
<form class="login-form" @submit.prevent="handleLogin">
<div class="form-group">
<label for="identity">邮箱</label>
<label for="identity">昵称 / 邮箱</label>
<el-input
id="identity"
v-model="identity"
type="email"
placeholder="请输入注册时的邮箱"
placeholder="请输入昵称或邮箱"
required
/>
</div>
+28 -1
View File
@@ -3,6 +3,7 @@
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { pb } from '@/api/pocketbase'
import PasswordInput from '@/components/common/PasswordInput.vue'
const router = useRouter()
@@ -17,6 +18,21 @@ const formError = ref('')
// ── 实时校验 ──
const nicknameOk = computed(() => nickname.value.length >= 2 && nickname.value.length <= 16)
const nicknameTaken = ref(false)
const nicknameChecking = ref(false)
async function checkNickname() {
const val = nickname.value.trim()
if (!val || val.length < 2) { nicknameTaken.value = false; return }
try {
nicknameChecking.value = true
const result = await pb.collection('users').getList(1, 1, {
filter: `name="${val}"`,
$autoCancel: false
})
nicknameTaken.value = result.items.length > 0
} catch { /* ignore */ } finally { nicknameChecking.value = false }
}
const hasLetter = computed(() => /[a-zA-Z]/.test(password.value))
const hasDigit = computed(() => /[0-9]/.test(password.value))
@@ -46,6 +62,10 @@ async function handleRegister() {
formError.value = '昵称需 2-16 个字符'
return
}
if (nicknameTaken.value) {
formError.value = '该昵称已被使用'
return
}
if (!passwordOk.value) {
formError.value = '密码不符合要求'
return
@@ -89,7 +109,10 @@ async function handleRegister() {
<!-- 昵称 -->
<div class="form-group">
<label for="nickname">昵称</label>
<el-input id="nickname" v-model="nickname" placeholder="2-16 个字符,支持中文" required />
<el-input id="nickname" v-model="nickname" placeholder="2-16 个字符,支持中文" required @blur="checkNickname" />
<span v-if="nicknameChecking" class="field-hint">检查中...</span>
<span v-else-if="nicknameTaken && nickname.length >= 2" class="field-hint error">该昵称已被使用</span>
<span v-else-if="nicknameOk && nickname" class="field-hint ok">昵称可用</span>
<div class="field-rules">
<span class="rule" :class="{ ok: nicknameOk && nickname }">2-16 个字符,支持中英文</span>
</div>
@@ -259,6 +282,10 @@ async function handleRegister() {
color: var(--gg-danger);
}
.field-hint.ok {
color: var(--gg-primary);
}
/* ── 密码强度 ── */
.password-strength {
display: flex;