一:介绍:

- 在登录页面,QQ登录图片处,赋予其打开QQ登录页面功能。
- 回跳的页面得到QQ给的唯一标识openId,根据openId去后台查询是否已经绑定过账户。
- 如果绑定过,完成登录。
- 没有绑定过
- 有账号的,绑定手机号,即为登录。
- 没账号的,完善账户信息,即为登录。
- 登录成功后,跳转首页,或者来源页面。
二: 如何申请QQ登录
参考文档:
1. [准备工作](https://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0)<br /> 2. [QQ互联JS_SDK](https://wiki.connect.qq.com/js_sdk%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E#3..E8.87.AA.E5.AE.9A.E4.B9.89.E7.99.BB.E5.BD.95.E6.8C.89.E9.92.AE)
- 自己要有一个已备案的,本身具有登录功能的网站。网站需要有QQ登录的逻辑(登录页面,回跳页面)。
- 在QQ互联上进行身份认证,审核通过。
- 在QQ互联上创建应用,填入自己网站的域名,备案号,回调地址。等待审核。
- 审核通过后,会给出:应用ID,应用key 回调地址。
以上四个步骤,一般由后端或运维完成,才能完成QQ登录
三: QQ登录按钮处理
测试:
这里已有测试使用的appid和uri (是通过了QQ官方认证)
# appid :100556005# redirect_uri :http://www.corho.com:8080/#/login/callback
在申请QQ登录功能成功之后,我们就可以使用qq提供的SDK工具来完成登录相关功能。
普通项目中使用
<script src="http://connect.qq.com/qc_jssdk.js" data-appid="100556005" data-redirecturi="http://www.corho.com:8080/#/login/callback"></script><span id="qqLoginBtn"></span><script>QC.Login({btnId: 'qqLoginBtn'})</script>
看页面生成QQ登录按钮,点击后新窗口打开

通过 QC.Login 就可以自动在页面上生成一个qq登录图标
点击后也可以跳转 但是看到效果也无法跳到指定页面
解决点击登录后打开一个新窗口

通过生成的代码是一个a标签包着一个img 我们只需要把链接整体拿过来 把window.open 之类的删掉 只有a链接的herf跳转
四: 在vue项目中使用
步骤一: 引入使用
第一步:
在public/index.html添加对.js文件的引用
<script src="http://connect.qq.com/qc_jssdk.js" data-appid="100556005" data-redirecturi="http://www.corho.com:8080/#/login/callback"></script>
注意,上面填写的data-appid和redirecturi是本项目申请的测试账号和密码。
第二步:
在业务组件中使用
import QC from 'qc' // 注意,我们并没有通过npm i qc 去安装包。而是在index.html中引入的
在模板中准备一个span(后边会删除它)
<span id="qqLoginBtn"></span>
调用QC.Login,生成按钮
onMounted(() => {// 组件渲染完毕,使用QC生成QQ登录按钮QC.Login({btnId: 'qqLoginBtn'})})
第三步:
在vue.config.js添加
// 这个是设置外部扩展,模块为qc变量名为QC,导入qc将不做打包。configureWebpack: {externals: {qc: 'QC'}},
告诉wepback,QC 是外部拓展:
- 如果遇到 import ‘qc’ 不要去node_modules下找了。
- npm run build时,也不要去打包 qc。
自动生成的代码转换a链接跳转
拿到调试工具中的a链接后把上面写的全删了
<a href="https://graph.qq.com/oauth2.0/authorize?client_id=100556005&response_type=token&scope=all&redirect_uri=http%3A%2F%2Fwww.corho.com%3A8080%2F%23%2Flogin%2Fcallback"><img src="https://qzonestyle.gtimg.cn/qzone/vas/opensns/res/img/Connect_logo_7.png" alt="QQ登录" border="0"></a>
步骤二: 将测试地址映射到本地
存在的问题:
登录之后的回调页面是[http://www.corho.com:8080/#/login/callback](http://www.corho.com:8080/#/login/callback) 这个地址不是localhost打头的<br />
目标:
让[http://www.corho.com:8080](http://www.corho.com:8080) <---->[http://localhost:8080](http://localhost:8080)
第一步: 修改host
由于域名是[www.corho.com](https://blog.csdn.net/m0_46846526/article/details/www.corho.com)和localhost不一致无法回调页面,需要在本地修改hosts地址。<br /> <br /> windows版本
1. 找到 C:\Windows\System32\drivers\etc 下hosts文件2. 在文件末尾中加入一行 127.0.0.1 www.corho.com3. 保存即可。# 如果提示没有权限1. 将hosts文件移到桌面,然后进行修改,确认保存。2. 将桌面hosts文件替换c盘文件
mac OS
1. 打开命令行窗口2. 输入:sudo vim /etc/hosts3. 按下:i 键4. 输入:127.0.0.1 www.corho.com5. 按下:esc6. 按下:shift + :7. 输入:wq 回车即可
第二步: 需要开启webpack服务器权限
在vue.config.js中,补充
// 这个是给webpack-dev-server开启可IP和域名访问权限。chainWebpack: config => {config.devServer.disableHostCheck(true)}
重启后,这时候再去尝试登录后就会调到 本地地址 但是本地地址没有设置路由 所以显示的是空页面
配置路由
{ path: '/login/callback', component: () => import('@/views/login/callback.vue') }
步骤三: 登录后的三条路线
- 已注册,已绑定 —-> 登录成功,跳转首页,或者来源页面
- 已注册,未绑定,绑定手机号 ——> 登录成功,跳转首页,或者来源页面
- 未注册,补充完善账户信息 ——->登录成功,跳转首页,或者来源页面
背景知识:
检查是否登录
QC.Login.check() :返回true|false, 用来检查是否登录
获取登录凭证Id
QC.Login.getMe(unionId=>{console.log(unionId)})
获取qq信息(头像 昵称)
QC.api('get_user_info').success(res=>console.log(res))
第一条路线:
拿qq返回的unionId调用本地接口
成功:返回的信息是用户信息,调用vuex中的actions保存信息 保存完之后跳转到主页
失败:在catch中原地不动 等待其他两条路线
<script>import LoginHeader from './components/loginHeader.vue'import LoginFooter from './components/loginFooter.vue'import { ref } from 'vue'import { userQQLogin } from '@/api/user'import { useStore } from 'vuex'import { useRouter } from 'vue-router'import Message from '@/components/XtxMessage.vue'import QC from 'qc'import CallbackBind from './components/callbackBind.vue'import CallbackPatch from './components/callbackPatch.vue'export default {name: 'PageCallback',components: { LoginHeader, LoginFooter, CallbackBind, CallbackPatch },setup () {const unionId = ref(null)const store = useStore()const router = useRouter()QC.Login.check() && QC.Login.getMe(openId => {console.log(openId)unionId.value = openIduserQQLogin(openId).then(data => {// 走到then说明成功// 1. 存储用户信息store.commit('user/setUser', data.result)// 跳转到主页router.push('/')// 弹框提示console.log(data)Message({ type: 'success', text: '登陆成功!' })}).catch(() => {// 走到catch说明失败 就留在此页面console.log('没有绑定 留此页面')})})const hasAccount = ref(true)return { hasAccount, unionId }}}</script>
第二条路线:
已有账号 请绑定手机 :意思是已有当前项目的账号了 直接绑定手机号

路线:
获取验证码 : 1. 判断手机号校验是否正确 2. 调用获取验证码接口
调用绑定接口: 1. 看用户名和验证码 表单校验是否正确 2.调用接口 3.提示文本 4.跳到主页
<script>import { ref, reactive } from 'vue'import QC from 'qc'import { Form, Field } from 'vee-validate'import { mobile, code } from '@/utils/validate'import { useCountDown } from '@/compositions/index'import { userQQBindCode } from '@/api/user'import Message from '@/components/message'import { useRouter } from 'vue-router'import { useStore } from 'vuex'export default {name: 'CallbackBind',components: {Form, Field},props: {unionId: {type: String,default: ''}},setup (props) {// 1. 定义数据项:qq头像 昵称const nickname = ref('')const avatar = ref('')QC.Login.check() && QC.api('get_user_info').success(res => {avatar.value = res.data.figureurl_2nickname.value = res.data.nickname}) // 获取当前登录的QQ账号的信息const target = ref(null)// 表单数据对象const formData = reactive({mobile: '13241051259',code: ''})// 校验规则const mySchema = {mobile: mobile,code: code}const store = useStore()const router = useRouter()const { start, time } = useCountDown()// 发送验证码倒计时const send = async () => {// 如果手机号格式不正确if (mobile(formData.mobile) !== true) {Message({ type: 'error', text: '手机号格式错误' })return}if (time >= 0) returntry {await userQQBindCode(formData.mobile).then((res) => {Message({ type: 'success', text: '获取验证码成功!' })start(60)})} catch (error) {Message({ type: 'warn', text: error.response.data.message + ', 请稍后重试' || '获取验证码失败!' })}}// 开始绑定const binding = () => {console.log(props.unionId)target.value.validate().then((vilid) => {// 开始绑定if (vilid) doBingding()}).catch(vlida => console.log(vlida))}const doBingding = async () => {try {await store.dispatch('user/userQQBindLogin', { unionId: props.unionId, mobile: formData.mobile, code: formData.code })Message({ type: 'success', text: '提交成功!' })router.push('/')} catch (error) {console.dir(error)Message({ type: 'error', text: error.message || '提交失败' })}}return { nickname, avatar, formData, target, mySchema, send, time, binding }}}</script>
第三条路线:
没有项目账号,也没有绑定手机号<br /><br />路线:<br />首先校验用户名 调用接口 看当前用户名是否存在 <br />校验密码 两次密码是否相等<br />获取验证码 : 1. 判断手机号校验是否正确 2. 调用获取验证码接口 <br />调用绑定接口: 1. 所有表单校验是否正确 2.调用接口 3.提示文本 4.跳到主页
<script>import { reactive, ref } from 'vue'import { Form, Field } from 'vee-validate'import { mobile, code, rePassword, password, accountApi as account } from '@/utils/validate'import { useCountDown } from '@/compositions/index'import Message from '@/components/message'import { userQQPatchCode } from '@/api/user'import { useRouter } from 'vue-router'import { useStore } from 'vuex'export default {name: 'CallbackPatch',components: {Form, Field},props: {unionId: {type: String,default: ''}},setup (props) {// 1. 表单校验 多两个校验:用户名是否存在,再次输入密码是否一致// 2. 发送短信验证码:接口API定义// 3. 完善信息// 表单数据对象const formData = reactive({account: null,mobile: null,code: null,password: null,rePassword: null})// 校验表单const mySchema = {account,mobile,code,password,rePassword}const { start, time } = useCountDown()// 发送验证码倒计时const send = async () => {// 如果手机号格式不正确if (mobile(formData.mobile) !== true) {Message({ type: 'error', text: '手机号格式错误' })return}if (time >= 0) returntry {await userQQPatchCode(formData.mobile).then((res) => {Message({ type: 'success', text: '获取验证码成功!' })start(60)})} catch (error) {Message({ type: 'warn', text: error.response.data.message + ', 请稍后重试' || '获取验证码失败!' })}}// 开始绑定const target = ref(null)const store = useStore()const router = useRouter()// 立即提交const submit = () => {console.log(props.openId)target.value.validate().then((vilid) => {// 开始绑定if (vilid) doSubmit()}).catch(vlida => console.log(vlida))}const doSubmit = async () => {try {await store.dispatch('user/userQQPatchLogin', {unionId: props.unionId,mobile: formData.mobile,code: formData.code,account: formData.account,password: formData.password})Message({ type: 'success', text: '提交成功!' })router.push('/')} catch (error) {console.dir(error)Message({ type: 'error', text: error.message || '提交失败' })}}return { formData, mySchema, send, time, submit, target }}}</script>
