目录
QQ登录 - 未绑定 - 有账号
如果账号是绑定的状态,手动调用一下解绑接口
http://pcapi-xiaotuxian-front.itheima.net/login/social/unbind?mobile=手机号
https://apipc-xiaotuxian-front.itheima.net/login/social/unbind?mobile=手机号
获取QQ头像和昵称
如果QQ没绑定过账号,需要绑定已存在的账号或者注册新的账号
(1)获取QQ信息
<script name="CallbackBind" lang="ts" setup>
import { QQUserInfo, QQUserInfoRes } from '@/types/user'
import { ref } from 'vue'
const qqInfo = ref<QQUserInfo>({} as QQUserInfo)
// 1. 判断QQ是否登录
if (QC.Login.check()) {
// 2. 获取QQ用户信息
QC.api('get_user_info').success((res: QQUserInfoRes) => {
console.log(res)
qqInfo.value = res.data
})
}
</script>
(2)提供数据类型
// QQ信息-用户详情
export interface QQUserInfo {
ret: number
msg: string
is_lost: number
nickname: string
gender: string
gender_type: number
province: string
city: string
year: string
constellation: string
figureurl: string
figureurl_1: string
figureurl_2: string
figureurl_qq_1: string
figureurl_qq_2: string
figureurl_qq: string
figureurl_type: string
is_yellow_vip: string
vip: string
yellow_vip_level: string
level: string
is_yellow_year_vip: string
}
// QQ返回信息
export interface QQUserInfoRes {
status: string
fmt: string
ret: number
code: number
data: QQUserInfo
seq: string
dataText: string
}
(3)渲染QQ信息
<div class="user-info">
<img :src="qqInfo.figureurl_2" alt="" />
<p>
Hi,{{ qqInfo.nickname }}
欢迎来小兔鲜,完成绑定后可以QQ账号一键登录哦~
</p>
</div>
表单校验
(1)提取校验规则 src/utils/validate.ts
export function accountRule(value: string) {
// value是将来使用该规则的表单元素的值
// 1. 必填
// 2. 6-20个字符,需要以字母开头
// 如何反馈校验成功还是失败,返回true才是成功,其他情况失败,返回失败原因。
if (!value) return '请输入用户名'
if (!/^[a-zA-Z]\w{5,19}$/.test(value)) return '字母开头且6-20个字符'
return true
}
export function passwordRule(value: string) {
if (!value) return '请输入密码'
if (!/^\w{6,24}$/.test(value)) return '密码是6-24个字符'
return true
}
export function mobileRule(value: string) {
if (!value) return '请输入手机号'
if (!/^1[3-9]\d{9}$/.test(value)) return '手机号格式错误'
return true
}
export function codeRule(value: string) {
if (!value) return '请输入验证码'
if (!/^\d{6}$/.test(value)) return '验证码是6个数字'
return true
}
export function isAgreeRule(value: string) {
if (!value) return '请勾选同意用户协议'
return true
}
(2)修改登录功能
import {
accountRule,
mobileRule,
codeRule,
passwordRule,
isAgreeRule
} from '@/utils/validate'
useForm({
validationSchema: {
account: accountRule,
mobile: mobileRule,
code: codeRule,
password: passwordRule,
isAgree: isAgreeRule
},
initialValues: {
mobile: '13666666666',
code: '123456',
account: 'xiaotuxian001',
password: '123456',
isAgree: true
}
})
(3)在 CallbackBind.vue
添加表单校验
// 表单校验
const { validate } = useForm({
validationSchema: {
mobile: mobileRule,
code: codeRule
}
})
const { value: mobile, errorMessage: mobileError } = useField('mobile')
const { value: code, errorMessage: codeError } = useField('code')
<div class="xtx-form-item">
<div class="field">
<i class="icon iconfont icon-phone"></i>
<input
class="input"
v-model="mobile"
type="text"
placeholder="绑定的手机号"
/>
</div>
<div class="error">{{ mobileError }}</div>
</div>
<div class="xtx-form-item">
<div class="field">
<i class="icon iconfont icon-code"></i>
<input
v-model="code"
class="input"
type="text"
placeholder="短信验证码"
/>
<span class="code">发送验证码</span>
</div>
<div class="error">{{ codeError }}</div>
</div>
(4)绑定时完成表单校验
<a href="javascript:;" class="submit" @click="bind">立即绑定</a>
const bind = async () => {
const res = await validate()
if (!res.valid) return
// 如果校验,发送请求进行绑定
}
发送验证码
(1)提供actions,用于获取短信绑定QQ
// 绑定qq的短信验证码
async sendQQBindMsg(mobile: string) {
await request.get('/login/social/code', {
params: {
mobile
}
})
}
(2)点击获取短信验证码时,发送请求获取验证码
const { time, start } = useCountDown()
const send = async () => {
if (time.value > 0) return
const res = await validateMobile()
console.log(res)
if (!res.valid) return
// 发送请求绑定qq
await user.sendQQBindMsg(mobile.value)
// 开启倒计时
start(10)
}
(3)渲染倒计时
<span class="code" @click="send">
{{ time === 0 ? '发送验证码' : `${time}s后发送` }}
</span>
QQ绑定完成
(1)提供绑定的actions
async qqBindLogin(openId: string, mobile: string, code: string) {
const res = await request.post<ApiRes<Profile>>('/login/social/bind', {
mobile,
code,
unionId: openId
})
// 1. 保存用户信息到 state 中
this.profile = res.data.result
setProfile(res.data.result)
},
(2)验证后,发送请求绑定
// 1. 判断QQ是否登录
if (QC.Login.check()) {
// 2. 获取QQ用户信息
QC.api('get_user_info').success((res: QQUserInfoRes) => {
console.log(res)
qqInfo.value = res.data
})
// 3. 获取openId
QC.Login.getMe((id) => {
openId = id
})
}
const bind = async () => {
const res = await validForm()
if (!res.valid) return
// 如果校验,发送请求进行绑定
await user.qqBindLogin(openId, mobile.value, code.value)
Message.success('QQ绑定成功')
router.push('/')
}
QQ登录 - 未绑定 - 无账号
(1)增加校验类型
export function rePasswordRule(value: string, { form }: any) {
if (!value) return '请输入确认密码'
if (!/^\w{6,24}$/.test(value)) return '密码是6-24个字符'
// 校验密码是否一致 form表单数据对象
if (value !== form.password) return '两次输入的密码不一致'
return true
}
(2)提供两个actions
async sendQQPathMsg(mobile: string) {
await request.get('/register/code', {
params: {
mobile
}
})
},
async qqPatchLogin(data: any) {
const res = await request.post<ApiRes<Profile>>(
`/login/social/${data.openId}/complement`,
data
)
// 1. 保存用户信息到 state 中
this.profile = res.data.result
setProfile(res.data.result)
}
(3)完整代码
<script lang="ts" setup name="CallbackPatch">
import {
useField,
useForm,
useValidateForm,
useValidateField
} from 'vee-validate'
import {
accountRule,
mobileRule,
codeRule,
passwordRule,
rePasswordRule
} from '@/utils/validate'
import { useCountDown } from '@/utils/hooks'
import Message from '@/components/message'
import useStore from '@/store'
import { useRouter } from 'vue-router'
const { user } = useStore()
const router = useRouter()
// 1. 获取openId
let openId = ''
// 判断QQ是否登录
if (QC.Login.check()) {
// 获取openId
QC.Login.getMe((id) => {
openId = id
})
}
// 表单校验
useForm({
validationSchema: {
account: accountRule,
mobile: mobileRule,
code: codeRule,
password: passwordRule,
repassword: rePasswordRule
}
})
const { errorMessage: accountError, value: account } =
useField<string>('account')
const { errorMessage: passwordError, value: password } =
useField<string>('password')
const { errorMessage: mobileError, value: mobile } = useField<string>('mobile')
const { errorMessage: codeError, value: code } = useField<string>('code')
const { errorMessage: repasswordError, value: repassword } =
useField<string>('repassword')
const validForm = useValidateForm()
const bind = async () => {
const res = await validForm()
if (!res.valid) return
await user.qqPatchLogin({
openId,
mobile: mobile.value,
code: code.value,
account: account.value,
password: password.value
})
Message.success('注册成功')
router.push('/')
}
// 获取验证码
const validMobile = useValidateField('mobile')
const { time, start } = useCountDown(60)
const send = async () => {
if (time.value > 0) return
// console.log('获取验证码')
// 单独校验手机号
const res = await validMobile()
if (!res.valid) {
return
}
await user.sendQQPathMsg(mobile.value)
Message.success('获取验证码成功')
start()
}
</script>
<template>
<div class="xtx-form">
<div class="xtx-form-item">
<div class="field">
<i class="icon iconfont icon-user"></i>
<input
class="input"
v-model="account"
type="text"
placeholder="请输入用户名"
/>
</div>
<div class="error">{{ accountError }}</div>
</div>
<div class="xtx-form-item">
<div class="field">
<i class="icon iconfont icon-phone"></i>
<input
class="input"
v-model="mobile"
type="text"
placeholder="请输入手机号"
/>
</div>
<div class="error">{{ mobileError }}</div>
</div>
<div class="xtx-form-item">
<div class="field">
<i class="icon iconfont icon-code"></i>
<input
class="input"
v-model="code"
type="text"
placeholder="请输入验证码"
/>
<span class="code" @click="send">{{
time === 0 ? '发送验证码' : `${time}s后发送`
}}</span>
</div>
<div class="error">{{ codeError }}</div>
</div>
<div class="xtx-form-item">
<div class="field">
<i class="icon iconfont icon-lock"></i>
<input
class="input"
v-model="password"
type="password"
placeholder="请输入密码"
/>
</div>
<div class="error">{{ passwordError }}</div>
</div>
<div class="xtx-form-item">
<div class="field">
<i class="icon iconfont icon-lock"></i>
<input
class="input"
v-model="repassword"
type="password"
placeholder="请确认密码"
/>
</div>
<div class="error">{{ repasswordError }}</div>
</div>
<a href="javascript:;" class="submit" @click="bind">立即提交</a>
</div>
</template>
<style scoped lang="less">
.code {
position: absolute;
right: 0;
top: 0;
line-height: 50px;
width: 80px;
color: #999;
&:hover {
cursor: pointer;
}
}
</style>