feat: 更换ui组件库,naiveui->element-plus

This commit is contained in:
2026-06-18 09:58:36 +08:00
parent ab9e77669c
commit 33c2b31117
16 changed files with 6192 additions and 528 deletions

View File

@@ -6,56 +6,66 @@
<span style="color: #71717a">登录账号以进入对战</span>
</p>
</div>
<n-form
<el-form
ref="formRef"
:model="loginModel"
:rules="rules"
:show-label="false"
hide-required-asterisk
class="login-wrapper__form"
>
<n-form-item path="username">
<n-input v-model:value="loginModel.username" placeholder="请输入账号" :maxlength="20" />
</n-form-item>
<n-form-item path="password">
<n-input
v-model:value="loginModel.password"
<el-form-item prop="username">
<el-input
size="large"
v-model="loginModel.username"
placeholder="请输入账号"
:maxlength="20"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginModel.password"
size="large"
type="password"
show-password-on="click"
show-password
placeholder="密码"
:maxlength="20"
/>
</n-form-item>
<n-form-item>
</el-form-item>
<el-form-item>
<div class="login-wrapper__actions">
<n-checkbox v-model:checked="rememberAccount">记住账号</n-checkbox>
<el-checkbox v-model="rememberAccount">记住账号</el-checkbox>
<a href="javascript:void(0)" style="color: #2080f0">忘记密码</a>
</div>
</n-form-item>
<n-form-item>
<n-button :loading="loading" style="width: 100%" type="primary" @click="userLoginFun"
</el-form-item>
<el-form-item>
<el-button :loading="loading" style="width: 100%" type="primary" @click="userLoginFun"
>登录
</n-button>
</n-form-item>
<n-form-item>
</el-button>
</el-form-item>
<el-form-item>
<div class="login-wrapper__quick-btns">
<n-button class="login-wrapper__quick-btn" @click="router.push('/auth/phone-login')"> 手机登录 </n-button>
<n-button class="login-wrapper__quick-btn" @click="router.push('/auth/qrcode-login')"> 扫码登录 </n-button>
<el-button class="login-wrapper__quick-btn" @click="router.push('/auth/phone-login')">
手机登录
</el-button>
<el-button class="login-wrapper__quick-btn" @click="router.push('/auth/qrcode-login')">
扫码登录
</el-button>
</div>
</n-form-item>
<n-form-item>
</el-form-item>
<el-form-item>
<div class="login-wrapper__register-link">
<span>没有账号</span>
<router-link to="/auth/register" style="color: #2080f0">创建账号</router-link>
</div>
</n-form-item>
</n-form>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, useTemplateRef, onMounted } from 'vue'
import type { FormItemRule, FormRules, FormInst } from 'naive-ui'
import { useMessage } from 'naive-ui'
import type { FormRules, FormInstance } from 'element-plus'
import { ElMessage } from 'element-plus'
import { useAccountStore } from '@/stores'
import { useRouter, useRoute } from 'vue-router'
@@ -65,8 +75,7 @@ const REMEMBER_KEY = 'login_remembered_username'
const router = useRouter()
const route = useRoute()
const accountStore = useAccountStore()
const message = useMessage()
const formRef = useTemplateRef<FormInst>('formRef')
const formRef = useTemplateRef<FormInstance>('formRef')
const loginModel = reactive({
username: '',
password: ''
@@ -84,43 +93,39 @@ onMounted(() => {
}
})
/** 账号验证规则 */
const validateUsername = (_rule: unknown, value: string, callback: (error?: Error) => void) => {
if (!value) {
callback(new Error('请输入登陆账号'))
} else if (value.length < 2) {
callback(new Error('账号长度不能小于2位'))
} else if (value.length > 20) {
callback(new Error('账号长度不能大于20位'))
} else if (!/^[A-Za-z][A-Za-z0-9]*$/.test(value)) {
callback(new Error('账号以字母开头,只能包含字母和数字'))
} else {
callback()
}
}
/** 密码验证规则 */
const validatePassword = (_rule: unknown, value: string, callback: (error?: Error) => void) => {
if (!value) {
callback(new Error('请输入密码'))
} else if (value.length < 8) {
callback(new Error('密码长度不能小于8位'))
} else if (value.length > 20) {
callback(new Error('密码长度不能大于20位'))
} else if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/.test(value)) {
callback(new Error('密码必须包含大小写字母和数字'))
} else {
callback()
}
}
const rules: FormRules = {
username: [
{
required: true,
trigger: ['blur'],
validator: (_rule: FormItemRule, value: string) => {
if (!value) {
return new Error('请输入登陆账号')
} else if (value.length < 2) {
return new Error('账号长度不能小于2位')
} else if (value.length > 20) {
return new Error('账号长度不能大于20位')
} else if (!/^[A-Za-z][A-Za-z0-9]*$/.test(value)) {
return new Error('账号以字母开头,只能包含字母和数字')
}
return true
}
}
],
password: [
{
required: true,
trigger: ['blur'],
validator: (_rule: FormItemRule, value: string) => {
if (!value) {
return new Error('请输入密码')
} else if (value.length < 8) {
return new Error('密码长度不能小于8位')
} else if (value.length > 20) {
return new Error('密码长度不能大于20位')
} else if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/.test(value)) {
return new Error('密码必须包含大小写字母和数字')
}
return true
}
}
]
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
}
/**
@@ -147,32 +152,34 @@ function getErrorMessage(error: unknown): string {
/** 用户登录 */
const userLoginFun = () => {
formRef.value?.validate(async (errors) => {
if (errors) return
formRef.value?.validate((valid: boolean) => {
if (!valid) return
loading.value = true
try {
await accountStore.login({
accountStore
.login({
username: loginModel.username,
password: loginModel.password
})
.then(() => {
// 记住账号:将用户名存入 localStorage
if (rememberAccount.value) {
localStorage.setItem(REMEMBER_KEY, loginModel.username)
} else {
localStorage.removeItem(REMEMBER_KEY)
}
// 记住账号:将用户名存入 localStorage
if (rememberAccount.value) {
localStorage.setItem(REMEMBER_KEY, loginModel.username)
} else {
localStorage.removeItem(REMEMBER_KEY)
}
message.success('登录成功')
// 跳转到目标页面(有 redirect 参数则优先跳回原页面,否则去首页)
const redirectPath = (route.query.redirect as string) || '/'
router.push(redirectPath)
} catch (error: unknown) {
message.error(getErrorMessage(error))
} finally {
loading.value = false
}
ElMessage.success('登录成功')
// 跳转到目标页面(有 redirect 参数则优先跳回原页面,否则去首页)
const redirectPath = (route.query.redirect as string) || '/'
router.push(redirectPath)
})
.catch((error: unknown) => {
ElMessage.error(getErrorMessage(error))
})
.finally(() => {
loading.value = false
})
})
}
</script>
@@ -204,7 +211,7 @@ const userLoginFun = () => {
.login-wrapper__form {
width: 100%;
margin-top: 40px;
margin-top: 20px;
}
.login-wrapper__actions {

View File

@@ -6,112 +6,106 @@
<span style="color: #71717a">请输入手机号码登录账号以进入对战</span>
</p>
</div>
<n-form
<el-form
ref="formRef"
:model="loginModel"
:rules="rules"
:show-label="false"
hide-required-asterisk
class="phone-login-wrapper__form"
>
<n-form-item path="phoneNumber">
<n-input v-model:value="loginModel.phoneNumber" placeholder="手机号码" :maxlength="20" />
</n-form-item>
<n-form-item path="code">
<el-form-item prop="phoneNumber">
<el-input
size="large"
v-model="loginModel.phoneNumber"
placeholder="手机号码"
:maxlength="11"
/>
</el-form-item>
<el-form-item prop="code">
<div class="phone-login-wrapper__code-row">
<n-input-otp
block
v-model:value="loginModel.code"
:allow-input="onlyAllowNumber"
style="gap: 0; flex: 2"
/>
<n-button style="flex: 1" @click="getPhoneCodeFun">获取验证码</n-button>
<el-input-otp
v-model="loginModel.code"
:length="6"
:formatter="onlyAllowNumber"
style="flex: 2; --el-input-otp-gap: 0"
>
<template #separator></template>
</el-input-otp>
<el-button style="flex: 1" @click="getPhoneCodeFun">获取验证码</el-button>
</div>
</n-form-item>
<n-form-item>
<n-button :loading="loading" style="width: 100%" type="primary" @click="phoneLoginFun"
</el-form-item>
<el-form-item>
<el-button :loading="loading" style="width: 100%" type="primary" @click="phoneLoginFun"
>登录
</n-button>
</n-form-item>
<n-form-item>
</el-button>
</el-form-item>
<el-form-item>
<div class="phone-login-wrapper__back-row">
<n-button class="phone-login-wrapper__back-btn" @click="router.push('/auth/login')"> 返回 </n-button>
<el-button class="phone-login-wrapper__back-btn" @click="router.push('/auth/login')">
返回
</el-button>
</div>
</n-form-item>
</n-form>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, useTemplateRef } from 'vue'
import type { FormItemRule, FormRules, FormInst } from 'naive-ui'
import type { FormRules, FormInstance } from 'element-plus'
import { useAccountStore } from '@/stores'
import { useRouter } from 'vue-router'
const router = useRouter()
const accountStore = useAccountStore()
const formRef = useTemplateRef<FormInst>('formRef')
const formRef = useTemplateRef<FormInstance>('formRef')
const loginModel = reactive({
phoneNumber: '',
code: []
code: ''
})
const loading = ref(false)
const onlyAllowNumber = (value: string) => !value || /^\d+$/.test(value)
/** 仅允许输入数字字符 */
const onlyAllowNumber = (value: string) => value.replace(/\D/g, '')
/** 手机号验证规则 */
const validatePhoneNumber = (_rule: unknown, value: string, callback: (error?: Error) => void) => {
if (!/^1[3-9]\d{9}$/.test(value)) {
callback(new Error('请输入正确的手机号码'))
} else {
callback()
}
}
/** 验证码验证规则 */
const validateCode = (_rule: unknown, value: string, callback: (error?: Error) => void) => {
if (!value || value.length !== 6) {
callback(new Error('请输入6位验证码'))
} else {
callback()
}
}
const rules: FormRules = {
phoneNumber: [
{
key: 'phoneNumber',
required: true,
trigger: ['blur'],
validator: (rule: FormItemRule, value: string) => {
if (!/^1[3-9]\d{9}$/.test(value)) {
return new Error('请输入手机号码')
}
return true
}
}
],
code: [
{
key: 'code',
required: true,
trigger: ['blur'],
validator: (_, value: string[] | null) => {
if (value === null) {
return new Error('请输入验证码')
}
if (value.filter(Boolean).length !== 6) {
return new Error('请输入验证码')
}
return true
}
}
]
phoneNumber: [{ required: true, trigger: 'blur', validator: validatePhoneNumber }],
code: [{ required: true, trigger: 'blur', validator: validateCode }]
}
const getPhoneCodeFun = () => {
formRef.value?.validate(
(errors) => {
if (!errors) {
console.log('获取验证码成功')
// TODO 获取验证码方法
}
},
(rule) => {
if (rule.key === 'phoneNumber') {
return true
}
return false
formRef.value?.validateField('phoneNumber', (valid: boolean) => {
if (valid) {
console.log('获取验证码成功')
// TODO 获取验证码方法
}
)
})
}
const phoneLoginFun = () => {
formRef.value?.validate((errors) => {
if (!errors) {
formRef.value?.validate((valid: boolean) => {
if (valid) {
loading.value = true
console.log('登录成功')
// TODO 登录方法
accountStore.token = '123456'
accountStore.accessToken = '123456'
setTimeout(() => {
loading.value = false
}, 1000)
@@ -147,7 +141,7 @@ const phoneLoginFun = () => {
.phone-login-wrapper__form {
width: 100%;
margin-top: 40px;
margin-top: 20px;
}
.phone-login-wrapper__code-row {

View File

@@ -6,30 +6,42 @@
<span style="color: #71717a">请用手机扫描二维码登录</span>
</p>
</div>
<n-form :show-label="false" class="qrcode-login-wrapper__form">
<n-form-item>
<n-space vertical style="align-items: center; flex: 1">
<n-qr-code v-model:value="qrcode" :size="180" />
<el-form hide-required-asterisk class="qrcode-login-wrapper__form">
<el-form-item>
<div class="qrcode-login-wrapper__qrcode-area">
<canvas ref="qrcodeCanvas" width="180" height="180"></canvas>
<p>扫码后点击 '确认'即可完成登录</p>
</n-space>
</n-form-item>
<n-form-item>
<div class="qrcode-login-wrapper__back-row">
<n-button class="qrcode-login-wrapper__back-btn" @click="router.push('/auth/login')"> 返回 </n-button>
</div>
</n-form-item>
</n-form>
</el-form-item>
<el-form-item>
<div class="qrcode-login-wrapper__back-row">
<el-button class="qrcode-login-wrapper__back-btn" @click="router.push('/auth/login')">
返回
</el-button>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useAccountStore } from '@/stores'
import { ref, useTemplateRef, onMounted } from 'vue'
import QRCode from 'qrcode'
import { useRouter } from 'vue-router'
const router = useRouter()
const accountStore = useAccountStore()
const qrcode = ref('')
const qrcodeCanvas = useTemplateRef<HTMLCanvasElement>('qrcodeCanvas')
/** 二维码内容TODO: 替换为实际登录凭证) */
const qrcodeValue = ref('https://example.com/login-qrcode')
onMounted(async () => {
if (qrcodeCanvas.value) {
await QRCode.toCanvas(qrcodeCanvas.value, qrcodeValue.value, {
width: 180,
margin: 2
})
}
})
</script>
<style scoped>
@@ -59,7 +71,14 @@ const qrcode = ref('')
.qrcode-login-wrapper__form {
width: 100%;
margin-top: 40px;
margin-top: 20px;
}
.qrcode-login-wrapper__qrcode-area {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.qrcode-login-wrapper__back-row {

View File

@@ -6,31 +6,33 @@
<span style="color: #71717a">创建账号进入对战</span>
</p>
</div>
<n-form
<el-form
ref="formRef"
:model="registerModel"
:rules="rules"
:show-label="false"
hide-required-asterisk
class="register-wrapper__form"
>
<n-form-item path="username">
<n-input v-model:value="registerModel.username" placeholder="请输入账号" :maxlength="20" />
</n-form-item>
<n-form-item path="password">
<el-form-item prop="username">
<el-input
size="large"
v-model="registerModel.username"
placeholder="请输入账号"
:maxlength="20"
/>
</el-form-item>
<el-form-item prop="password">
<div class="register-wrapper__password-group">
<n-input
v-model:value="registerModel.password"
<el-input
v-model="registerModel.password"
size="large"
type="password"
show-password-on="click"
show-password
placeholder="密码"
:maxlength="20"
/>
<div class="register-wrapper__strength-bars">
<div
v-for="level in 5"
:key="level"
class="register-wrapper__strength-bar-track"
>
<div v-for="level in 5" :key="level" class="register-wrapper__strength-bar-track">
<div
class="register-wrapper__strength-bar-fill"
:style="{ width: securityLevel >= level ? '100%' : '0' }"
@@ -41,47 +43,48 @@
使用 8 个或更多字符混合大小写字母数字
</p>
</div>
</n-form-item>
<n-form-item path="confirmPassword">
<n-input
v-model:value="registerModel.confirmPassword"
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="registerModel.confirmPassword"
size="large"
type="password"
show-password-on="click"
show-password
placeholder="确认密码"
:maxlength="20"
/>
</n-form-item>
<n-form-item path="agreement">
</el-form-item>
<el-form-item prop="agreement">
<div class="register-wrapper__agreement">
<n-checkbox v-model:checked="registerModel.agreement">我同意</n-checkbox>
<el-checkbox v-model="registerModel.agreement">我同意</el-checkbox>
<a href="javascript:void(0)" style="color: #2080f0">隐私政策 & 条款</a>
</div>
</n-form-item>
<n-form-item>
<n-button :loading="loading" style="width: 100%" type="primary" @click="userRegisterFun"
</el-form-item>
<el-form-item>
<el-button :loading="loading" style="width: 100%" type="primary" @click="userRegisterFun"
>注册
</n-button>
</n-form-item>
<n-form-item>
</el-button>
</el-form-item>
<el-form-item>
<div class="register-wrapper__login-link">
<span>已经有账号了</span>
<router-link to="/auth/login" style="color: #2080f0">去登录</router-link>
</div>
</n-form-item>
</n-form>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, useTemplateRef, watch } from 'vue'
import { type FormInst, type FormItemRule, type FormRules, useMessage } from 'naive-ui'
import type { FormRules, FormInstance } from 'element-plus'
import { ElMessage } from 'element-plus'
import { useAccountStore } from '@/stores'
import { useRouter } from 'vue-router'
const router = useRouter()
const accountStore = useAccountStore()
const message = useMessage()
const formRef = useTemplateRef<FormInst>('formRef')
const formRef = useTemplateRef<FormInstance>('formRef')
const registerModel = reactive({
username: '',
password: '',
@@ -116,70 +119,65 @@ const checkPasswordStrength = (password: string) => {
securityLevel.value = strength
}
/** 账号验证规则 */
const validateUsername = (_rule: unknown, value: string, callback: (error?: Error) => void) => {
if (!value) {
callback(new Error('请输入登陆账号'))
} else if (value.length < 2) {
callback(new Error('账号长度不能小于2位'))
} else if (value.length > 20) {
callback(new Error('账号长度不能大于20位'))
} else if (!/^[A-Za-z][A-Za-z0-9]*$/.test(value)) {
callback(new Error('账号以字母开头,只能包含字母和数字'))
} else {
callback()
}
}
/** 密码验证规则 */
const validatePassword = (_rule: unknown, value: string, callback: (error?: Error) => void) => {
if (!value) {
callback(new Error('请输入密码'))
} else if (value.length < 8) {
callback(new Error('密码长度不能小于8位'))
} else if (value.length > 20) {
callback(new Error('密码长度不能大于20位'))
} else if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/.test(value)) {
callback(new Error('密码必须包含大小写字母和数字'))
} else {
callback()
}
}
/** 确认密码验证规则 */
const validateConfirmPassword = (
_rule: unknown,
value: string,
callback: (error?: Error) => void
) => {
if (!value) {
callback(new Error('请再次输入密码'))
} else if (registerModel.password !== value) {
callback(new Error('密码不一致'))
} else {
callback()
}
}
/** 协议同意验证规则 */
const validateAgreement = (_rule: unknown, value: boolean, callback: (error?: Error) => void) => {
if (!value) {
callback(new Error('请勾选同意协议'))
} else {
callback()
}
}
const rules: FormRules = {
username: [
{
required: true,
trigger: ['blur'],
validator: (_rule: FormItemRule, value: string) => {
if (!value) {
return new Error('请输入登陆账号')
} else if (value.length < 2) {
return new Error('账号长度不能小于2位')
} else if (value.length > 20) {
return new Error('账号长度不能大于20位')
} else if (!/^[A-Za-z][A-Za-z0-9]*$/.test(value)) {
return new Error('账号以字母开头,只能包含字母和数字')
}
return true
}
}
],
password: [
{
required: true,
trigger: ['blur'],
validator: (_rule: FormItemRule, value: string) => {
if (!value) {
return new Error('请输入密码')
} else if (value.length < 8) {
return new Error('密码长度不能小于8位')
} else if (value.length > 20) {
return new Error('密码长度不能大于20位')
} else if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/.test(value)) {
return new Error('密码必须包含大小写字母和数字')
}
return true
}
}
],
confirmPassword: [
{
required: true,
trigger: ['blur'],
validator: (_rule: FormItemRule, value: string) => {
if (!value) {
return new Error('请再次输入密码')
}
if (registerModel.password !== value) {
return new Error('密码不一致')
}
return true
}
}
],
agreement: [
{
required: true,
trigger: 'change',
validator: (_rule: FormItemRule, value: boolean) => {
if (!value) {
return new Error('请勾选同意协议')
}
return true
}
}
]
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }],
confirmPassword: [{ required: true, trigger: 'blur', validator: validateConfirmPassword }],
agreement: [{ required: true, trigger: 'change', validator: validateAgreement }]
}
/**
@@ -205,24 +203,26 @@ function getErrorMessage(error: unknown): string {
/** 用户注册 */
const userRegisterFun = () => {
formRef.value?.validate(async (errors) => {
if (errors) return
formRef.value?.validate((valid: boolean) => {
if (!valid) return
loading.value = true
try {
await accountStore.register({
accountStore
.register({
username: registerModel.username,
password: registerModel.password
})
message.success('注册成功')
// 注册成功后跳转首页
router.push('/')
} catch (error: unknown) {
message.error(getErrorMessage(error))
} finally {
loading.value = false
}
.then(() => {
ElMessage.success('注册成功')
// 注册成功后跳转首页
router.push('/')
})
.catch((error: unknown) => {
ElMessage.error(getErrorMessage(error))
})
.finally(() => {
loading.value = false
})
})
}
</script>
@@ -254,7 +254,7 @@ const userRegisterFun = () => {
.register-wrapper__form {
width: 100%;
margin-top: 40px;
margin-top: 20px;
}
.register-wrapper__password-group {
@@ -291,6 +291,8 @@ const userRegisterFun = () => {
.register-wrapper__password-hint {
font-size: 12px;
margin-top: 8px;
margin-bottom: 0;
line-height: normal;
}
.register-wrapper__agreement {