feat: 更换ui组件库,naiveui->element-plus
This commit is contained in:
34
src/App.vue
34
src/App.vue
@@ -1,35 +1,11 @@
|
||||
<template>
|
||||
<NConfigProvider
|
||||
:locale="zhCN"
|
||||
:date-locale="dateZhCN"
|
||||
:theme="lightTheme"
|
||||
:theme-overrides="themeOverrides"
|
||||
class="app-wrapper"
|
||||
>
|
||||
<NNotificationProvider>
|
||||
<NMessageProvider>
|
||||
<router-view />
|
||||
</NMessageProvider>
|
||||
</NNotificationProvider>
|
||||
</NConfigProvider>
|
||||
<ElConfigProvider :locale="zhCn">
|
||||
<router-view />
|
||||
</ElConfigProvider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router'
|
||||
import { zhCN, dateZhCN, lightTheme, useThemeVars, type GlobalThemeOverrides } from 'naive-ui'
|
||||
|
||||
const themeOverrides: GlobalThemeOverrides = {
|
||||
common: {
|
||||
primaryColor: '#2080F0',
|
||||
primaryColorHover: '#4098fc',
|
||||
primaryColorPressed: '#1060c9',
|
||||
primaryColorSuppl: '#4098fc'
|
||||
}
|
||||
}
|
||||
import { ElConfigProvider } from 'element-plus'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { createDiscreteApi } from 'naive-ui'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { eventBus, AppEvents } from '@/utils'
|
||||
import type { RequestErrorEvent } from './types'
|
||||
|
||||
/** 离散式 API(可在组件外使用) */
|
||||
const { message } = createDiscreteApi(['message'])
|
||||
|
||||
/**
|
||||
* 注册请求错误 UI 展示
|
||||
*
|
||||
@@ -13,5 +10,5 @@ const { message } = createDiscreteApi(['message'])
|
||||
*/
|
||||
eventBus.on(AppEvents.REQUEST_ERROR, (payload: unknown) => {
|
||||
const { message: errorMessage } = payload as RequestErrorEvent
|
||||
message.error(errorMessage)
|
||||
ElMessage.error(errorMessage)
|
||||
})
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="user-bar">
|
||||
<div class="container">
|
||||
<n-popover trigger="click">
|
||||
<template #trigger>
|
||||
<el-popover trigger="click" :width="260">
|
||||
<template #reference>
|
||||
<div class="user-info">
|
||||
<n-badge dot :offset="[-7, 29]" color="#008000">
|
||||
<n-avatar round src="https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg" />
|
||||
</n-badge>
|
||||
<el-badge is-dot :offset="[-7, 29]" class="user-badge">
|
||||
<el-avatar shape="circle" src="https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg" />
|
||||
</el-badge>
|
||||
<div class="user-name">
|
||||
<div class="user-title">MarchGlow</div>
|
||||
<div class="user-state">在线</div>
|
||||
@@ -14,17 +14,17 @@
|
||||
</div>
|
||||
</template>
|
||||
<span>I wish they all could be California girls</span>
|
||||
</n-popover>
|
||||
</el-popover>
|
||||
|
||||
<div class="feature">
|
||||
<n-icon color="#fff" :size="26"><SettingsSharp /></n-icon>
|
||||
<el-icon color="#fff" :size="26"><Setting /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SettingsSharp } from '@vicons/ionicons5'
|
||||
import { Setting } from '@element-plus/icons-vue'
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -36,11 +36,15 @@ import { SettingsSharp } from '@vicons/ionicons5'
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.n-badge .n-badge-sup) {
|
||||
.user-badge :deep(.el-badge__content) {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.user-badge :deep(.el-badge__content.is-dot) {
|
||||
background-color: #008000;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
@@ -6,27 +6,22 @@
|
||||
<RouterView v-slot="{ Component, route }">
|
||||
<Transition appear mode="out-in" name="slide-right">
|
||||
<KeepAlive :include="['Login']">
|
||||
<component
|
||||
:is="Component"
|
||||
:key="route.fullPath"
|
||||
class="auth-layout__content"
|
||||
/>
|
||||
<component :is="Component" :key="route.fullPath" class="auth-layout__content" />
|
||||
</KeepAlive>
|
||||
</Transition>
|
||||
</RouterView>
|
||||
<div class="auth-layout__footer">
|
||||
<span style="color: #71717a">Copyright © 2024</span>
|
||||
<a href="javascript:void(0)" class="auth-layout__footer-link" style="color: #2080f0">MarchGlow</a>
|
||||
<a href="javascript:void(0)" class="auth-layout__footer-link" style="color: #2080f0"
|
||||
>MarchGlow</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useThemeVars } from 'naive-ui'
|
||||
const themeVars = useThemeVars()
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style scoped>
|
||||
.auth-layout {
|
||||
@@ -54,7 +49,7 @@ const themeVars = useThemeVars()
|
||||
background: linear-gradient(
|
||||
154deg,
|
||||
#07070915 30%,
|
||||
color-mix(in srgb, v-bind(themeVars.primaryColor) 30%, transparent) 48%,
|
||||
color-mix(in srgb, var(--el-color-primary) 30%, transparent) 48%,
|
||||
#07070915 64%
|
||||
);
|
||||
filter: blur(100px);
|
||||
@@ -94,7 +89,6 @@ const themeVars = useThemeVars()
|
||||
}
|
||||
|
||||
.auth-layout__content {
|
||||
margin-top: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import './assets/style'
|
||||
import 'element-plus/dist/index.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { initPinia } from './stores'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user