feat(auth): 实现用户登录和实名认证功能

- 新增用户登录接口和相关逻辑
- 实现实名认证页面,包括表单填写和提交功能
- 添加用户信息存储和展示
- 优化页面样式和交互
This commit is contained in:
xingyy 2025-01-17 14:07:19 +08:00
parent d345b66026
commit fbbb30040e
5 changed files with 212 additions and 71 deletions

View File

@ -7,3 +7,17 @@ export async function senCode(data) {
body: data, body: data,
}) })
} }
export async function userLogin(data) {
const http = getHttp()
return await http('/api/v1/m/user/login', {
method: 'POST',
body: data,
})
}
export async function userUpdate(data) {
const http = getHttp()
return await http('/api/v1/m/user/update', {
method: 'POST',
body: data,
})
}

View File

@ -18,7 +18,6 @@ export function setupHttp() {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
async onRequest({ options }) { async onRequest({ options }) {
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
options.headers = { options.headers = {
...options.headers, ...options.headers,
...(token && { Authorization: token }), ...(token && { Authorization: token }),
@ -26,11 +25,10 @@ export function setupHttp() {
}, },
async onResponse({ response }) { async onResponse({ response }) {
if (response._data.status===1){ if (response._data.status===1){
message.warning(response._data.msg) message.error(response._data.msg)
} }
}, },
async onResponseError({ response }) { async onResponseError({ response }) {
console.log('error错误')
const { message } = response._data const { message } = response._data
if (Array.isArray(message)) { if (Array.isArray(message)) {
message.forEach((item) => { message.forEach((item) => {

View File

@ -2,8 +2,9 @@
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import countryCode from '../countryRegion/data/index.js' import countryCode from '../countryRegion/data/index.js'
import {senCode} from "@/api/auth/index.js"; import {senCode, userLogin} from "@/api/auth/index.js";
import {authStore} from "~/stores/auth/index.js";
const {userInfo,token}= authStore()
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const { locale } = useI18n() const { locale } = useI18n()
@ -30,8 +31,8 @@ const startCountdown=()=> {
}, 1000); }, 1000);
} }
const countdown = ref(0); const countdown = ref(0);
const phoneNum = ref('') const phoneNum = ref('17630920520')
const code = ref('') const code = ref('655119')
const pane = ref(0) const pane = ref(0)
const showKeyboard = ref(false); const showKeyboard = ref(false);
// //
@ -78,19 +79,23 @@ watch(locale, () => {
}) })
const vanSwipeRef=ref(null) const vanSwipeRef=ref(null)
const getCode =async () => { const getCode =async () => {
loadingRef.value.loading1=true // loadingRef.value.loading1=true
const res=await senCode({ // const res=await senCode({
telNum:phoneNum.value, // telNum:phoneNum.value,
zone:selectedZone.value // zone:selectedZone.value
}) // })
loadingRef.value.loading1=false // loadingRef.value.loading1=false
if (res.status===0){ // if (res.status===0){
pane.value = 1 // pane.value = 1
vanSwipeRef.value?.swipeTo(pane.value) // vanSwipeRef.value?.swipeTo(pane.value)
showKeyboard.value=true // showKeyboard.value=true
startCountdown(); // startCountdown();
//
} // }
pane.value = 1
vanSwipeRef.value?.swipeTo(pane.value)
showKeyboard.value=true
startCountdown();
} }
const goBack = () => { const goBack = () => {
@ -98,9 +103,20 @@ const goBack = () => {
pane.value = 0 pane.value = 0
vanSwipeRef.value?.swipeTo(pane.value) vanSwipeRef.value?.swipeTo(pane.value)
} }
const goLogin = () => { const goLogin =async () => {
router.push('/realAuth'); const res=await userLogin({
} telNum:phoneNum.value,
zone:selectedZone.value,
code:code.value
})
if (res.status===0){
userInfo.value=res.data.accountInfo
token.value=res.data.token
if (!res.data.isReal){
router.push('/realAuth');
}
}
}
</script> </script>
<template> <template>

View File

@ -0,0 +1,65 @@
<script setup>
import {authStore} from "@/stores/auth/index.js";
const props = defineProps({
type: {
type: Number,
default: 0
}
})
const {userInfo}= authStore()
</script>
<template>
<div class="text-#1A1A1A text-16px">
<template v-if="type===0">
<div class="flex mb-20px" >
<div class="mr-10px">姓名</div>
<div>{{userInfo.realName}}</div>
</div>
<div class="flex mb-20px">
<div class="mr-10px">性别</div>
<div>{{userInfo.sex}}</div>
</div>
<div class="flex mb-20px">
<div class="mr-10px">出生日期</div>
<div>{{userInfo.birthDate}}</div>
</div>
<div class="flex">
<div class="mr-10px">身份证号</div>
<div>{{userInfo.idNum}}</div>
</div>
</template>
<template v-if="type===1">
<div class="flex mb-20px" >
<div class="mr-10px">姓名</div>
<div>{{userInfo.realName}}</div>
</div>
<div class="flex mb-20px">
<div class="mr-10px">性别</div>
<div>{{userInfo.sex}}</div>
</div>
<div class="flex mb-20px">
<div class="mr-10px">出生日期</div>
<div>{{userInfo.birthDate}}</div>
</div>
<div class="flex">
<div class="mr-10px">家庭住址</div>
<div>{{userInfo.idNum}}</div>
</div>
<div class="flex">
<div class="mr-10px">所属银行</div>
<div>{{userInfo.idNum}}</div>
</div>
<div class="flex">
<div class="mr-10px">银行卡号码</div>
<div>{{userInfo.idNum}}</div>
</div>
</template>
</div>
</template>
<style scoped>
</style>

View File

@ -1,34 +1,78 @@
<script setup> <script setup>
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import {userUpdate} from "~/api/auth/index.js";
import {message} from '@/components/x-message/useMessage.js'
import detail from './components/detail.vue'
import {authStore} from "~/stores/auth/index.js";
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const showPicker = ref(false); const showPicker = ref(false);
const pickerValue = ref([]); const {userInfo}= authStore()
const gender = ref('男')
const birthday = ref('')
const birthdayDate = ref([]) const birthdayDate = ref([])
const showBirthdayPicker = ref(false) const showBirthdayPicker = ref(false)
const minDate = new Date(1950, 0, 1) const minDate = new Date(1950, 0, 1)
const maxDate = new Date(2025, 12, 31) const maxDate = new Date(2025, 12, 31)
const active=ref(0)
const { t } = useI18n() const { t } = useI18n()
const form=ref({
const columns = computed(() => [ idNum: "",
{ text: t('realAuth.male'), value: t('realAuth.male') }, realName: "",
{ text: t('realAuth.female'), value: t('realAuth.female') }, sex:'',
]); birthDate:'',
userExtend: {
address: "",
bankName: "",
bankNo: ""
}
})
const form1=ref({
idNum:'',
realName:''
})
const columns=ref([
{ text: t('realAuth.male'), value: 1 },
{ text: t('realAuth.female'), value: 2 },
])
const onConfirm = ({ selectedValues, selectedOptions }) => { const onConfirm = ({ selectedValues, selectedOptions }) => {
pickerValue.value = selectedValues form.value.sex=selectedValues?.[0]
gender.value = selectedOptions[0].text
showPicker.value = false showPicker.value = false
} }
const onBirthdayConfirm = (value) => { const onBirthdayConfirm = (value) => {
birthdayDate.value = value.selectedValues form.value.birthDate=value.selectedValues.join('-')
birthday.value = value.selectedValues.join('-')
showBirthdayPicker.value = false showBirthdayPicker.value = false
} }
function isFormComplete(obj) {
for (const key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
if (!isFormComplete(obj[key])) {
return false;
}
} else if (obj[key] === "") {
return false;
}
}
return true;
}
const statusCode=ref(0)
const confirm=async ()=>{
const thatForm=active.value===0?form1.value:form.value
if (isFormComplete(thatForm)){
const res=await userUpdate(thatForm)
if (res.status===0){
userInfo.value=res.data
message.success('提交成功')
statusCode.value=1
}
}else {
message.error('请填写身份证相关信息')
}
}
const goHome=()=>{
router.push('/')
}
definePageMeta({ definePageMeta({
title: '实名认证', title: '实名认证',
i18n: 'realAuth.title', i18n: 'realAuth.title',
@ -36,27 +80,21 @@ definePageMeta({
</script> </script>
<template> <template>
<div class="px-[31px] bg-#fff w-100vw h-100vh pt-[46px]"> <div class="px-[31px] bg-[url('@/static/images/asdfsdd.png')] bg-cover w-100vw flex-grow-1 pt-[46px] relative flex flex-col">
<van-tabs animated swipeable> <van-tabs v-if="statusCode===0" v-model:active="active" animated swipeable>
<van-tab :title="$t('realAuth.cnTab')" class="pt-[80px]"> <van-tab :title="$t('realAuth.cnTab')" class="pt-[80px]">
<div class="text-[#BDBDBD] text-[16px] mb-[34px]">{{ $t('realAuth.cnTabDesc') }}</div> <template v-if="statusCode===0">
<div class="mb-[100px]"> <div class="text-[#BDBDBD] text-[16px] mb-[34px]">{{ $t('realAuth.cnTabDesc') }}</div>
<div class="border-b-[1.7px] mt-[8px]"> <div class="mb-[100px]">
<van-field :label="$t('realAuth.idCard')" clearable <div class="border-b-[1.7px] mt-[8px]">
:placeholder="$t('realAuth.idCardPlaceholder')"></van-field> <van-field v-model="form1.idNum" :label="$t('realAuth.idCard')" clearable
:placeholder="$t('realAuth.idCardPlaceholder')"></van-field>
</div>
<div class="border-b-[1.7px] mt-[8px]">
<van-field v-model="form1.realName" :label="$t('realAuth.name')" clearable :placeholder="$t('realAuth.namePlaceholder')"></van-field>
</div>
</div> </div>
<div class="border-b-[1.7px] mt-[8px]"> </template>
<van-field :label="$t('realAuth.name')" clearable :placeholder="$t('realAuth.namePlaceholder')"></van-field>
</div>
</div>
<div class="flex justify-between">
<van-button style="width: 151px;height: 48px" color="#E9F1F8">
<div class="text-#2B53AC text-16px">{{ $t('realAuth.cancel') }}</div>
</van-button>
<van-button style="width: 151px;height: 48px" color="#2B53AC">
<div class="text-#FFFFFF text-16px">{{ $t('realAuth.confirm') }}</div>
</van-button>
</div>
</van-tab> </van-tab>
<van-tab :title="$t('realAuth.otherTab')" class="pt-[80px]"> <van-tab :title="$t('realAuth.otherTab')" class="pt-[80px]">
<div class="text-[#BDBDBD] text-[16px] mb-[34px]">{{ $t('realAuth.otherTabDesc') }}</div> <div class="text-[#BDBDBD] text-[16px] mb-[34px]">{{ $t('realAuth.otherTabDesc') }}</div>
@ -65,39 +103,49 @@ definePageMeta({
<van-field :label="$t('realAuth.name')" clearable :placeholder="$t('realAuth.namePlaceholder')"></van-field> <van-field :label="$t('realAuth.name')" clearable :placeholder="$t('realAuth.namePlaceholder')"></van-field>
</div> </div>
<div class="border-b-[1.7px] mt-[8px]"> <div class="border-b-[1.7px] mt-[8px]">
<van-field v-model="gender" is-link readonly name="picker" :label="$t('realAuth.gender')" <van-field :modelValue="columns.find(x=>x.value===form.sex)?.text" is-link readonly name="picker" :label="$t('realAuth.gender')"
@click="showPicker = true" /> @click="showPicker = true" />
</div> </div>
<div class="border-b-[1.7px] mt-[8px]"> <div class="border-b-[1.7px] mt-[8px]">
<van-field v-model="birthday" is-link readonly name="birthdayPicker" :label="$t('realAuth.birthday')" <van-field v-model="form.birthDate" is-link readonly name="birthdayPicker" :label="$t('realAuth.birthday')"
:placeholder="$t('realAuth.birthdayPlaceholder')" @click="showBirthdayPicker = true" /> :placeholder="$t('realAuth.birthdayPlaceholder')" @click="showBirthdayPicker = true" />
</div> </div>
<div class="border-b-[1.7px] mt-[8px]"> <div class="border-b-[1.7px] mt-[8px]">
<van-field :label="$t('realAuth.adress')" clearable <van-field v-model="form.userExtend.address" :label="$t('realAuth.adress')" clearable
:placeholder="$t('realAuth.adressPlaceholder')"></van-field> :placeholder="$t('realAuth.adressPlaceholder')"></van-field>
</div> </div>
<div class="border-b-[1.7px] mt-[8px]"> <div class="border-b-[1.7px] mt-[8px]">
<van-field :label="$t('realAuth.bank')" clearable :placeholder="$t('realAuth.bankPlaceholder')"></van-field> <van-field v-model="form.userExtend.bankName" :label="$t('realAuth.bank')" clearable :placeholder="$t('realAuth.bankPlaceholder')"></van-field>
</div> </div>
<div class="border-b-[1.7px] mt-[8px]"> <div class="border-b-[1.7px] mt-[8px]">
<van-field :label="$t('realAuth.bankCard')" clearable <van-field v-model="form.userExtend.bankNo" :label="$t('realAuth.bankCard')" clearable
:placeholder="$t('realAuth.bankCardPlaceholder')"></van-field> :placeholder="$t('realAuth.bankCardPlaceholder')"></van-field>
</div> </div>
<div class="flex justify-between mt-[100px]">
<van-button style="width: 151px;height: 48px" color="#E9F1F8">
<div class="text-#2B53AC text-16px">{{ $t('realAuth.cancel') }}</div>
</van-button>
<van-button style="width: 151px;height: 48px" color="#2B53AC">
<div class="text-#FFFFFF text-16px">{{ $t('realAuth.confirm') }}</div>
</van-button>
</div>
</div> </div>
</van-tab> </van-tab>
</van-tabs> </van-tabs>
<van-tabs v-else-if="statusCode===1" v-model:active="active" animated swipeable>
<van-tab :title="$t('realAuth.cnTab')" class="pt-[80px]">
<detail :type="active"></detail>
</van-tab>
<van-tab :title="$t('realAuth.otherTab')" class="pt-[80px]">
<detail :type="active"></detail>
</van-tab>
</van-tabs>
<div class="flex justify-between" v-if="statusCode===0">
<van-button style="width: 151px;height: 48px" color="#E9F1F8">
<div class="text-#2B53AC text-16px">{{ $t('realAuth.cancel') }}</div>
</van-button>
<van-button @click="confirm" style="width: 151px;height: 48px" color="#2B53AC">
<div class="text-#FFFFFF text-16px">{{ $t('realAuth.confirm') }}</div>
</van-button>
</div>
<div v-else class="mt-auto pb-94px">
<van-button color="#E9F1F8" @click="goHome" style="color: #2B53AC;font-weight: 600" block>去首页</van-button>
</div>
<van-popup v-model:show="showPicker" destroy-on-close position="bottom"> <van-popup v-model:show="showPicker" destroy-on-close position="bottom">
<van-picker :columns="columns" :model-value="pickerValue" @confirm="onConfirm" @cancel="showPicker = false" /> <van-picker :columns="columns" @confirm="onConfirm" @cancel="showPicker = false" />
</van-popup> </van-popup>
<van-popup v-model:show="showBirthdayPicker" destroy-on-close position="bottom"> <van-popup v-model:show="showBirthdayPicker" destroy-on-close position="bottom">
<van-date-picker v-model="birthdayDate" :min-date="minDate" :max-date="maxDate" <van-date-picker v-model="birthdayDate" :min-date="minDate" :max-date="maxDate"