feat: 添加vite的proxy代理设置

This commit is contained in:
2026-06-09 14:03:42 +08:00
parent 44b0de35df
commit 5b53e33e27
8 changed files with 442 additions and 19 deletions

6
components.d.ts vendored
View File

@@ -25,8 +25,11 @@ declare module 'vue' {
NFormItem: typeof import('naive-ui')['NFormItem'] NFormItem: typeof import('naive-ui')['NFormItem']
NGlobalStyle: typeof import('naive-ui')['NGlobalStyle'] NGlobalStyle: typeof import('naive-ui')['NGlobalStyle']
NInput: typeof import('naive-ui')['NInput'] NInput: typeof import('naive-ui')['NInput']
NInputOtp: typeof import('naive-ui')['NInputOtp']
NMessageProvider: typeof import('naive-ui')['NMessageProvider'] NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NQrCode: typeof import('naive-ui')['NQrCode']
NSpace: typeof import('naive-ui')['NSpace']
NThemeEditor: typeof import('naive-ui')['NThemeEditor'] NThemeEditor: typeof import('naive-ui')['NThemeEditor']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
@@ -48,8 +51,11 @@ declare global {
const NFormItem: typeof import('naive-ui')['NFormItem'] const NFormItem: typeof import('naive-ui')['NFormItem']
const NGlobalStyle: typeof import('naive-ui')['NGlobalStyle'] const NGlobalStyle: typeof import('naive-ui')['NGlobalStyle']
const NInput: typeof import('naive-ui')['NInput'] const NInput: typeof import('naive-ui')['NInput']
const NInputOtp: typeof import('naive-ui')['NInputOtp']
const NMessageProvider: typeof import('naive-ui')['NMessageProvider'] const NMessageProvider: typeof import('naive-ui')['NMessageProvider']
const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
const NQrCode: typeof import('naive-ui')['NQrCode']
const NSpace: typeof import('naive-ui')['NSpace']
const NThemeEditor: typeof import('naive-ui')['NThemeEditor'] const NThemeEditor: typeof import('naive-ui')['NThemeEditor']
const RouterLink: typeof import('vue-router')['RouterLink'] const RouterLink: typeof import('vue-router')['RouterLink']
const RouterView: typeof import('vue-router')['RouterView'] const RouterView: typeof import('vue-router')['RouterView']

View File

@@ -15,12 +15,22 @@ const routes: RouteRecordRaw[] = [
{ {
path: 'login', path: 'login',
name: 'Login', name: 'Login',
component: () => import('@/views/auth/login.vue') component: () => import('@/views/auth/Login.vue')
}, },
{ {
path: 'register', path: 'register',
name: 'Register', name: 'Register',
component: () => import('@/views/auth/register.vue') component: () => import('@/views/auth/Register.vue')
},
{
path: 'phone-login',
name: 'PhoneLogin',
component: () => import('@/views/auth/PhoneLogin.vue')
},
{
path: 'qrcode-login',
name: 'QRCodeLogin',
component: () => import('@/views/auth/QRCodeLogin.vue')
} }
] ]
} }

View File

@@ -0,0 +1,30 @@
import { customRef } from 'vue'
/**
* 创建防抖的响应式引用
* @param value - 初始值
* @param delay - 防抖延迟时间(毫秒),默认 200ms
* @returns 防抖的响应式引用
*/
export function useDebouncedRef<T>(value: T, delay: number = 300) {
let timeout: ReturnType<typeof setTimeout> | null = null
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue: T) {
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}

View File

@@ -32,14 +32,20 @@
</div> </div>
</n-form-item> </n-form-item>
<n-form-item> <n-form-item>
<n-button :loading="loginLoading" style="width: 100%" type="primary" @click="userLoginFun" <n-button :loading="loading" style="width: 100%" type="primary" @click="userLoginFun"
>登录</n-button >登录
> </n-button>
</n-form-item>
<n-form-item>
<div class="flex-center flex-1 gap-4">
<n-button class="flex-1" @click="router.push('/auth/phone-login')"> 手机登录 </n-button>
<n-button class="flex-1" @click="router.push('/auth/qrcode-login')"> 扫码登录 </n-button>
</div>
</n-form-item> </n-form-item>
<n-form-item> <n-form-item>
<div class="flex-center flex-1"> <div class="flex-center flex-1">
<span>没有账号</span> <span>没有账号</span>
<router-link to="/auth/register" style="color: #2080f0">注册</router-link> <router-link to="/auth/register" style="color: #2080f0">创建账号</router-link>
</div> </div>
</n-form-item> </n-form-item>
</n-form> </n-form>
@@ -50,23 +56,26 @@
import { reactive, ref, useTemplateRef } from 'vue' import { reactive, ref, useTemplateRef } from 'vue'
import type { FormItemRule, FormRules, FormInst } from 'naive-ui' import type { FormItemRule, FormRules, FormInst } from 'naive-ui'
import { useAccountStore } from '@/stores' import { useAccountStore } from '@/stores'
import { useRouter } from 'vue-router'
const router = useRouter()
const accountStore = useAccountStore() const accountStore = useAccountStore()
const formRef = useTemplateRef<FormInst>('formRef') const formRef = useTemplateRef<FormInst>('formRef')
const loginModel = reactive({ const loginModel = reactive({
username: '', username: '',
password: '' password: ''
}) })
const loginLoading = ref(false) const loading = ref(false)
const rules: FormRules = { const rules: FormRules = {
username: [ username: [
{ {
required: true, required: true,
message: '请输入登陆账号',
trigger: ['blur'], trigger: ['blur'],
validator: (rule: FormItemRule, value: string) => { validator: (rule: FormItemRule, value: string) => {
if (value.length < 2) { if (!value) {
return new Error('请输入登陆账号')
} else if (value.length < 2) {
return new Error('账号长度不能小于2位') return new Error('账号长度不能小于2位')
} else if (value.length > 20) { } else if (value.length > 20) {
return new Error('账号长度不能大于20位') return new Error('账号长度不能大于20位')
@@ -80,11 +89,12 @@ const rules: FormRules = {
password: [ password: [
{ {
required: true, required: true,
message: '请输入登陆密码',
trigger: ['blur'], trigger: ['blur'],
validator: (rule: FormItemRule, value: string) => { validator: (rule: FormItemRule, value: string) => {
if (value.length < 6) { if (!value) {
return new Error('密码长度不能小于6位') return new Error('请输入密码')
} else if (value.length < 8) {
return new Error('密码长度不能小于8位')
} else if (value.length > 20) { } else if (value.length > 20) {
return new Error('密码长度不能大于20位') return new Error('密码长度不能大于20位')
} else if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/.test(value)) { } else if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/.test(value)) {
@@ -99,12 +109,12 @@ const rules: FormRules = {
const userLoginFun = () => { const userLoginFun = () => {
formRef.value?.validate((errors) => { formRef.value?.validate((errors) => {
if (!errors) { if (!errors) {
loginLoading.value = true loading.value = true
console.log('登录成功') console.log('登录成功')
// TODO // TODO
accountStore.token = '123456' accountStore.token = '123456'
setTimeout(() => { setTimeout(() => {
loginLoading.value = false loading.value = false
}, 1000) }, 1000)
} }
}) })

View File

@@ -0,0 +1,123 @@
<template>
<div class="flex-col-center">
<div class="w-full">
<h2 class="text-3xl/9 mb-3 font-bold lg:text-4xl">欢迎回来 📲</h2>
<p>
<span style="color: #71717a">请输入手机号码登录账号以进入对战</span>
</p>
</div>
<n-form
ref="formRef"
:model="loginModel"
:rules="rules"
:show-label="false"
class="w-full mt-10"
>
<n-form-item path="phoneNumber">
<n-input v-model:value="loginModel.phoneNumber" placeholder="手机号码" :maxlength="20" />
</n-form-item>
<n-form-item path="code">
<div class="flex flex-1 gap-4">
<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>
</div>
</n-form-item>
<n-form-item>
<n-button :loading="loading" style="width: 100%" type="primary" @click="phoneLoginFun"
>登录
</n-button>
</n-form-item>
<n-form-item>
<div class="flex-center flex-1 gap-4">
<n-button class="flex-1" @click="router.push('/auth/login')"> 返回 </n-button>
</div>
</n-form-item>
</n-form>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, useTemplateRef } from 'vue'
import type { FormItemRule, FormRules, FormInst } from 'naive-ui'
import { useAccountStore } from '@/stores'
import { useRouter } from 'vue-router'
const router = useRouter()
const accountStore = useAccountStore()
const formRef = useTemplateRef<FormInst>('formRef')
const loginModel = reactive({
phoneNumber: '',
code: []
})
const loading = ref(false)
const onlyAllowNumber = (value: string) => !value || /^\d+$/.test(value)
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
}
}
]
}
const getPhoneCodeFun = () => {
formRef.value?.validate(
(errors) => {
if (!errors) {
console.log('获取验证码成功')
// TODO 获取验证码方法
}
},
(rule) => {
if (rule.key === 'phoneNumber') {
return true
}
return false
}
)
}
const phoneLoginFun = () => {
formRef.value?.validate((errors) => {
if (!errors) {
loading.value = true
console.log('登录成功')
// TODO 登录方法
accountStore.token = '123456'
setTimeout(() => {
loading.value = false
}, 1000)
}
})
}
</script>
<style scoped></style>

View File

@@ -0,0 +1,35 @@
<template>
<div class="flex-col-center">
<div class="w-full">
<h2 class="text-3xl/9 mb-3 font-bold lg:text-4xl">欢迎回来 📱</h2>
<p>
<span style="color: #71717a">请用手机扫描二维码登录</span>
</p>
</div>
<n-form :show-label="false" class="w-full mt-10">
<n-form-item>
<n-space vertical style="align-items: center; flex: 1">
<n-qr-code v-model:value="qrcode" :size="180" />
<p>扫码后点击 '确认'即可完成登录</p>
</n-space>
</n-form-item>
<n-form-item>
<div class="flex-center flex-1 gap-4">
<n-button class="flex-1" @click="router.push('/auth/login')"> 返回 </n-button>
</div>
</n-form-item>
</n-form>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useAccountStore } from '@/stores'
import { useRouter } from 'vue-router'
const router = useRouter()
const accountStore = useAccountStore()
const qrcode = ref('')
</script>
<style scoped></style>

214
src/views/auth/Register.vue Normal file
View File

@@ -0,0 +1,214 @@
<template>
<div class="flex-col-center">
<div class="w-full">
<h2 class="text-3xl/9 mb-3 font-bold lg:text-4xl">创建一个账号 🚀</h2>
<p>
<span style="color: #71717a">创建账号进入对战</span>
</p>
</div>
<n-form
ref="formRef"
:model="registerModel"
:rules="rules"
:show-label="false"
class="w-full mt-10"
>
<n-form-item path="username">
<n-input v-model:value="registerModel.username" placeholder="请输入账号" :maxlength="20" />
</n-form-item>
<n-form-item path="password">
<div class="flex flex-col flex-1">
<n-input
v-model:value="registerModel.password"
type="password"
show-password-on="mousedown"
placeholder="密码"
:maxlength="20"
/>
<div class="flex flex-1 gap-1 mt-2">
<div class="flex-1 h-2 rounded-xl relative bg-gray-200 overflow-hidden">
<div
:class="securityLevel > 0 ? 'w-full' : 'w-0'"
class="absolute left-0 top-0 h-full bg-red-500 transition-all duration-500"
></div>
</div>
<div class="flex-1 h-2 rounded-xl relative bg-gray-200 overflow-hidden">
<div
:class="securityLevel > 1 ? 'w-full' : 'w-0'"
class="absolute left-0 top-0 h-full bg-red-500 transition-all duration-500"
></div>
</div>
<div class="flex-1 h-2 rounded-xl relative bg-gray-200 overflow-hidden">
<div
:class="securityLevel > 2 ? 'w-full' : 'w-0'"
class="absolute left-0 top-0 h-full bg-red-500 transition-all duration-500"
></div>
</div>
<div class="flex-1 h-2 rounded-xl relative bg-gray-200 overflow-hidden">
<div
:class="securityLevel > 3 ? 'w-full' : 'w-0'"
class="absolute left-0 top-0 h-full bg-red-500 transition-all duration-500"
></div>
</div>
<div class="flex-1 h-2 rounded-xl relative bg-gray-200 overflow-hidden">
<div
:class="securityLevel > 4 ? 'w-full' : 'w-0'"
class="absolute left-0 top-0 h-full bg-red-500 transition-all duration-500"
></div>
</div>
</div>
<p class="text-xs mt-2" style="color: #71717a">
使用 8 个或更多字符混合大小写字母数字
</p>
</div>
</n-form-item>
<n-form-item path="confirmPassword">
<n-input
v-model:value="registerModel.confirmPassword"
type="password"
show-password-on="mousedown"
placeholder="确认密码"
:maxlength="20"
/>
</n-form-item>
<n-form-item path="agreement">
<div class="flex flex-1">
<n-checkbox v-model:checked="registerModel.agreement">我同意</n-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"
>登录
</n-button>
</n-form-item>
<n-form-item>
<div class="flex-center flex-1">
<span>已经有账号了</span>
<router-link to="/auth/login" style="color: #2080f0">去登录</router-link>
</div>
</n-form-item>
</n-form>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, useTemplateRef, watch } from 'vue'
import { type FormInst, type FormItemRule, type FormRules } from 'naive-ui'
const formRef = useTemplateRef<FormInst>('formRef')
const registerModel = reactive({
username: '',
password: '',
confirmPassword: '',
agreement: false
})
const loading = ref(false)
const securityLevel = ref(0)
watch(
() => registerModel.password,
(value) => {
checkPasswordStrength(value)
}
)
const checkPasswordStrength = (password: string) => {
let strength = 0
if (password.length >= 8) {
strength++
}
if (/[a-z]/.test(password)) {
strength++
}
if (/[A-Z]/.test(password)) {
strength++
}
if (/[0-9]/.test(password)) {
strength++
}
strength = Math.min(strength, 5)
securityLevel.value = strength
}
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) => {
console.log(value)
if (!value) {
return new Error('请勾选同意协议')
}
return true
}
}
]
}
const userRegisterFun = () => {
formRef.value?.validate((errors) => {
if (!errors) {
loading.value = true
console.log('注册成功')
// TODO 注册方法
setTimeout(() => {
loading.value = false
}, 1000)
}
})
}
</script>
<style scoped></style>

View File

@@ -1,5 +0,0 @@
<template>$END$</template>
<script setup lang="ts"></script>
<style scoped></style>