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:
@@ -4,6 +4,7 @@ import { ref } from 'vue'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { pb } from '@/api/pocketbase'
|
||||||
import PasswordInput from '@/components/common/PasswordInput.vue'
|
import PasswordInput from '@/components/common/PasswordInput.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -15,16 +16,29 @@ const loading = ref(false)
|
|||||||
|
|
||||||
async function handleLogin() {
|
async function handleLogin() {
|
||||||
if (!identity.value || !password.value) {
|
if (!identity.value || !password.value) {
|
||||||
ElMessage.warning('请输入邮箱和密码')
|
ElMessage.warning('请输入昵称/邮箱和密码')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
await userStore.login(identity.value, password.value)
|
let loginIdentity = identity.value.trim()
|
||||||
|
|
||||||
const redirect = '/'
|
// 如果不包含 @,按昵称或用户名查找对应 username 用于认证
|
||||||
router.push(redirect)
|
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) {
|
} catch (error: any) {
|
||||||
ElMessage.error(error.message || '登录失败')
|
ElMessage.error(error.message || '登录失败')
|
||||||
} finally {
|
} finally {
|
||||||
@@ -45,12 +59,11 @@ async function handleLogin() {
|
|||||||
|
|
||||||
<form class="login-form" @submit.prevent="handleLogin">
|
<form class="login-form" @submit.prevent="handleLogin">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="identity">邮箱</label>
|
<label for="identity">昵称 / 邮箱</label>
|
||||||
<el-input
|
<el-input
|
||||||
id="identity"
|
id="identity"
|
||||||
v-model="identity"
|
v-model="identity"
|
||||||
type="email"
|
placeholder="请输入昵称或邮箱"
|
||||||
placeholder="请输入注册时的邮箱"
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { pb } from '@/api/pocketbase'
|
||||||
import PasswordInput from '@/components/common/PasswordInput.vue'
|
import PasswordInput from '@/components/common/PasswordInput.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -17,6 +18,21 @@ const formError = ref('')
|
|||||||
|
|
||||||
// ── 实时校验 ──
|
// ── 实时校验 ──
|
||||||
const nicknameOk = computed(() => nickname.value.length >= 2 && nickname.value.length <= 16)
|
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 hasLetter = computed(() => /[a-zA-Z]/.test(password.value))
|
||||||
const hasDigit = computed(() => /[0-9]/.test(password.value))
|
const hasDigit = computed(() => /[0-9]/.test(password.value))
|
||||||
@@ -46,6 +62,10 @@ async function handleRegister() {
|
|||||||
formError.value = '昵称需 2-16 个字符'
|
formError.value = '昵称需 2-16 个字符'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (nicknameTaken.value) {
|
||||||
|
formError.value = '该昵称已被使用'
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!passwordOk.value) {
|
if (!passwordOk.value) {
|
||||||
formError.value = '密码不符合要求'
|
formError.value = '密码不符合要求'
|
||||||
return
|
return
|
||||||
@@ -89,7 +109,10 @@ async function handleRegister() {
|
|||||||
<!-- 昵称 -->
|
<!-- 昵称 -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="nickname">昵称</label>
|
<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">
|
<div class="field-rules">
|
||||||
<span class="rule" :class="{ ok: nicknameOk && nickname }">2-16 个字符,支持中英文</span>
|
<span class="rule" :class="{ ok: nicknameOk && nickname }">2-16 个字符,支持中英文</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -259,6 +282,10 @@ async function handleRegister() {
|
|||||||
color: var(--gg-danger);
|
color: var(--gg-danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.field-hint.ok {
|
||||||
|
color: var(--gg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── 密码强度 ── */
|
/* ── 密码强度 ── */
|
||||||
.password-strength {
|
.password-strength {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user