feat(auth): 添加验证码功能并优化登录注册页面 #1

Merged
minglipro merged 1 commits from dev into master 2025-08-20 14:07:42 +08:00
6 changed files with 147 additions and 41 deletions

View File

@ -1,5 +1,5 @@
import alova, { type AlovaResponse } from '@/plugins/alova'; import alova, { type AlovaResponse } from '@/plugins/alova';
import type { LoginBody, RegisterBody, ResUserInfo } from '@/apis/auth/type.ts'; import type { LoginBody, RegisterBody, ResCaptcha, ResUserInfo } from '@/apis/auth/type.ts';
export const login = (lb: LoginBody) => export const login = (lb: LoginBody) =>
alova.Post<AlovaResponse<string>>('/auth/login', lb, { alova.Post<AlovaResponse<string>>('/auth/login', lb, {
@ -14,3 +14,8 @@ export const register = (rb: RegisterBody) => {
export const info = () => { export const info = () => {
return alova.Get<AlovaResponse<ResUserInfo>>('/auth/info', { meta: { notbefore: true } }); return alova.Get<AlovaResponse<ResUserInfo>>('/auth/info', { meta: { notbefore: true } });
}; };
export const captcha = () =>
alova.Get<AlovaResponse<ResCaptcha>>('/auth/captcha', {
meta: { notbefore: true },
});

View File

@ -1,6 +1,7 @@
export interface LoginBody { export interface LoginBody {
passWold: string; passWold: string;
userName: string; userName: string;
uuid: string;
} }
export interface RegisterBody { export interface RegisterBody {
@ -27,3 +28,7 @@ export interface ResUserInfo {
user: User; user: User;
login: boolean; login: boolean;
} }
export interface ResCaptcha {
captcha: string;
uuid: string;
}

View File

@ -14,10 +14,18 @@
<div v-loading="regis_login" class="card" style="margin-top: 20px"> <div v-loading="regis_login" class="card" style="margin-top: 20px">
<div f-c-c> <div f-c-c>
<NGradientText v-if="data.carData.p_result === 2 && data.carData.status === 2" size="65">可上磅</NGradientText> <NGradientText v-if="data.carData.p_result === 2 && data.carData.status === 2" size="65">可上磅</NGradientText>
<NGradientText v-if="data.carData.p_result === 40 && data.carData.status === 1" size="65">已出库</NGradientText> <NGradientText v-else-if="data.carData.p_result === 40 && data.carData.status === 1" size="65">
<NGradientText v-if="data.carData.p_result === 2 && data.carData.status === 0" size="65">已入库</NGradientText> 已撤销
<NGradientText v-if="data.carData.p_result === 40 && data.carData.status === 2" size="65">已撤销</NGradientText> </NGradientText>
<NGradientText v-if="data.carData.p_result === 2 && data.carData.status === 1" size="65">已出库</NGradientText> <NGradientText v-else-if="data.carData.p_result === 2 && data.carData.status === 0" size="65">
已入库
</NGradientText>
<NGradientText v-else-if="data.carData.p_result === 40 && data.carData.status === 2" size="65">
已撤销
</NGradientText>
<NGradientText v-else-if="data.carData.p_result === 2 && data.carData.status === 1" size="65">
已出库
</NGradientText>
<NGradientText v-else size="65" type="error">不可上磅</NGradientText> <NGradientText v-else size="65" type="error">不可上磅</NGradientText>
</div> </div>
<div class="card-line"></div> <div class="card-line"></div>
@ -58,13 +66,7 @@ import { onMounted, ref } from 'vue';
import { getMyCar } from '@/apis/bacth'; import { getMyCar } from '@/apis/bacth';
import { UserStore } from '@/plugins'; import { UserStore } from '@/plugins';
import { import { NAlert, NButton, NDescriptions, NDescriptionsItem, NGradientText } from 'naive-ui';
NAlert,
NButton,
NDescriptions,
NDescriptionsItem,
NGradientText
} from 'naive-ui';
import type { CarDataResType } from '@/apis/bacth/type.ts'; import type { CarDataResType } from '@/apis/bacth/type.ts';
import UseApiLoading from '@/utils/UseApiLoading.ts'; import UseApiLoading from '@/utils/UseApiLoading.ts';
@ -99,6 +101,7 @@ function getdata() {
getMyCars().then((a) => { getMyCars().then((a) => {
console.log(a.json().Data); console.log(a.json().Data);
data.value = a.json().Data; data.value = a.json().Data;
console.log(data.value);
}); });
} }
onMounted(() => { onMounted(() => {

View File

@ -17,6 +17,21 @@
<NFormItem label="密码" path="passWold"> <NFormItem label="密码" path="passWold">
<NInput v-model:value="fromdata.passWold" placeholder="请输入密码" type="password" /> <NInput v-model:value="fromdata.passWold" placeholder="请输入密码" type="password" />
</NFormItem> </NFormItem>
<NFormItem label="验证码" path="captcha">
<div style="display: flex; width: 100%">
<NInput
v-model:value="fromdata.captcha"
placeholder="请输入验证码"
style="flex-grow: 1; border-bottom-right-radius: 0; border-top-right-radius: 0" />
<img
:src="cpaimg"
b-r
height="34"
style="border-bottom-right-radius: 3px; border-top-right-radius: 3px"
width="85"
@click="captchas" />
</div>
</NFormItem>
<NFormItem label=" "> <NFormItem label=" ">
<NCheckbox v-model:checked="userStore.userDataRemberData.renb">记住我</NCheckbox> <NCheckbox v-model:checked="userStore.userDataRemberData.renb">记住我</NCheckbox>
</NFormItem> </NFormItem>
@ -33,20 +48,33 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { type FormInst, NButton, NCheckbox, NForm, NFormItem, NGradientText, NInput, NText } from 'naive-ui'; import {
type FormInst,
type FormItemRule,
NButton,
NCheckbox,
NForm,
NFormItem,
NGradientText,
NInput,
NText,
} from 'naive-ui';
import { onMounted, reactive, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue';
import message from '@/utils/message.ts'; import message from '@/utils/message.ts';
import { login } from '@/apis/auth'; import { captcha, login } from '@/apis/auth';
import { UserStore } from '@/plugins'; import { UserStore } from '@/plugins';
import UseApiLoading from '@/utils/UseApiLoading.ts'; import UseApiLoading from '@/utils/UseApiLoading.ts';
const formRef = ref<FormInst | null>(null); const formRef = ref<FormInst | null>(null);
const cpaimg = ref('');
const userStore = UserStore(); const userStore = UserStore();
const fromdata = reactive({ const fromdata = reactive({
passWold: '', passWold: '',
userName: '', userName: '',
captcha: '',
uuid: '',
}); });
onMounted(() => { onMounted(() => {
@ -54,6 +82,7 @@ onMounted(() => {
fromdata.userName = userStore.userDataRemberData.user; fromdata.userName = userStore.userDataRemberData.user;
fromdata.passWold = userStore.userDataRemberData.pass; fromdata.passWold = userStore.userDataRemberData.pass;
} }
captchas();
}); });
const [[loading_login], logins] = UseApiLoading(login); const [[loading_login], logins] = UseApiLoading(login);
@ -61,20 +90,31 @@ const [[loading_login], logins] = UseApiLoading(login);
const regis = () => { const regis = () => {
formRef.value?.validate((errors: any) => { formRef.value?.validate((errors: any) => {
if (!errors) { if (!errors) {
logins(fromdata).then((a) => { logins(fromdata)
userStore.ishaslogin = true; .then((a) => {
userStore.token = a.json().Data; userStore.ishaslogin = true;
if (userStore.userDataRemberData.renb) { userStore.token = a.json().Data;
userStore.userDataRemberData.user = fromdata.userName; if (userStore.userDataRemberData.renb) {
userStore.userDataRemberData.pass = fromdata.passWold; userStore.userDataRemberData.user = fromdata.userName;
} userStore.userDataRemberData.pass = fromdata.passWold;
window.location.replace('/'); }
}); window.location.replace('/');
})
.catch(() => {
captchas();
fromdata.captcha = '';
});
} else { } else {
message.error('请确认 填写信息是否正确'); message.error('请确认 填写信息是否正确');
} }
}); });
}; };
const captchas = () => {
captcha().then((a) => {
fromdata.uuid = a.json().Data.uuid;
cpaimg.value = a.json().Data.captcha;
});
};
const rules = { const rules = {
userName: { userName: {
@ -89,6 +129,17 @@ const rules = {
message: '请输入密码', message: '请输入密码',
trigger: ['input'], trigger: ['input'],
}, },
captcha: {
key: 'captcha',
required: true,
message: '验证码格式不正确 4位数字',
trigger: ['input'],
validator(rule: FormItemRule, value: string) {
const a = RegExp('^\\d{4}$').test(value);
console.log(a);
return a;
},
},
}; };
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -23,6 +23,21 @@
<NFormItem label="确认密码" path="passre"> <NFormItem label="确认密码" path="passre">
<NInput v-model:value="fromdata.passre" placeholder="请输入确认密码" type="password" /> <NInput v-model:value="fromdata.passre" placeholder="请输入确认密码" type="password" />
</NFormItem> </NFormItem>
<NFormItem label="验证码" path="captcha">
<div style="display: flex; width: 100%">
<NInput
v-model:value="fromdata.captcha"
placeholder="请输入验证码"
style="flex-grow: 1; border-bottom-right-radius: 0; border-top-right-radius: 0" />
<img
:src="cpaimg"
b-r
height="34"
style="border-bottom-right-radius: 3px; border-top-right-radius: 3px"
width="85"
@click="captchas" />
</div>
</NFormItem>
</NForm> </NForm>
<div f-c-c> <div f-c-c>
<NText style="padding-right: 5px">我已已经有账号了</NText> <NText style="padding-right: 5px">我已已经有账号了</NText>
@ -46,33 +61,44 @@ import {
NInput, NInput,
NText NText
} from 'naive-ui'; } from 'naive-ui';
import { reactive, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue';
import message from '@/utils/message.ts'; import message from '@/utils/message.ts';
import { register } from '@/apis/auth'; import { captcha, register } from '@/apis/auth';
import { router, UserStore } from '@/plugins'; import { UserStore } from '@/plugins';
import UseApiLoading from '@/utils/UseApiLoading.ts'; import UseApiLoading from '@/utils/UseApiLoading.ts';
const cpaimg = ref('');
const formRef = ref<FormInst | null>(null); const formRef = ref<FormInst | null>(null);
const userStore = UserStore(); const userStore = UserStore();
const fromdata = reactive({ tel: '', car: '', pass: '', passre: '' }); const fromdata = reactive({ tel: '', car: '', pass: '', passre: '', captcha: '', uuid: '' });
const [[regis_login], regise] = UseApiLoading(register); const [[regis_login], regise] = UseApiLoading(register);
const regis = () => { const regis = () => {
formRef.value?.validate((errors: any) => { formRef.value?.validate((errors: any) => {
if (!errors) { if (!errors) {
regise(fromdata).then((a) => { regise(fromdata)
userStore.ishaslogin = true; .then((a) => {
userStore.token = a.json().Data; userStore.ishaslogin = true;
userStore.getLogin(); userStore.token = a.json().Data;
router.push({ name: 'index' }); userStore.getLogin();
}); window.location.replace('/');
})
.catch(() => {
captchas();
fromdata.captcha = '';
});
} else { } else {
message.error('请确认 填写信息是否正确'); message.error('请确认 填写信息是否正确');
} }
}); });
}; };
const captchas = () => {
captcha().then((a) => {
fromdata.uuid = a.json().Data.uuid;
cpaimg.value = a.json().Data.captcha;
});
};
const rules = { const rules = {
tel: { tel: {
key: 'tel', key: 'tel',
@ -97,14 +123,10 @@ const rules = {
pass: { pass: {
key: 'pass', key: 'pass',
required: true, required: true,
message: message: '最小8位最大256位',
'密码强度低\n至少包含字母、数字、特殊字符最少9位并且不能连续出现3个大小连续或相同的数字(如456、654、888)',
trigger: ['input'], trigger: ['input'],
validator(rule: FormItemRule, value: string) { min: 8,
return RegExp( max: 256,
'^(?=.*\\d)(?!.*(\\d)\\1{2})(?!.*(012|123|234|345|456|567|678|789|987|876|765|654|543|432|321|210))(?=.*[a-zA-Z])(?=.*[^\\da-zA-Z\\s]).{1,9}$',
).test(value);
},
}, },
passre: { passre: {
key: 'passre', key: 'passre',
@ -115,6 +137,18 @@ const rules = {
return fromdata.pass === value; return fromdata.pass === value;
}, },
}, },
captcha: {
key: 'captcha',
required: true,
message: '验证码格式不正确 4位数字',
trigger: ['input'],
validator(rule: FormItemRule, value: string) {
const a = RegExp('^\\d{4}$').test(value);
console.log(a);
return a;
},
},
}; };
onMounted(captchas);
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -3,6 +3,10 @@
html[theme="dark"] { html[theme="dark"] {
background-color: #202020; background-color: #202020;
color: #d8d8d8; color: #d8d8d8;
*[b-r] {
border: 1px solid rgb(62, 62, 62);
}
} }
*[f-c-c] { *[f-c-c] {
@ -15,3 +19,7 @@ a {
color: #1bbb44; color: #1bbb44;
text-decoration: none; text-decoration: none;
} }
*[b-r] {
border: 1px solid rgba(194, 194, 194, 1);
}