一:介绍:

image.png

  • 在登录页面,QQ登录图片处,赋予其打开QQ登录页面功能。
  • 回跳的页面得到QQ给的唯一标识openId,根据openId去后台查询是否已经绑定过账户。
    • 如果绑定过,完成登录。
    • 没有绑定过
      • 有账号的,绑定手机号,即为登录。
      • 没账号的,完善账户信息,即为登录。
  • 登录成功后,跳转首页,或者来源页面。

二: 如何申请QQ登录

参考文档:

  1. 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)
  1. 自己要有一个已备案的,本身具有登录功能的网站。网站需要有QQ登录的逻辑(登录页面,回跳页面)。
  2. 在QQ互联上进行身份认证,审核通过。
  3. 在QQ互联上创建应用,填入自己网站的域名,备案号,回调地址。等待审核。
  4. 审核通过后,会给出:应用ID,应用key 回调地址。

以上四个步骤,一般由后端或运维完成,才能完成QQ登录

三: QQ登录按钮处理

测试:

这里已有测试使用的appid和uri (是通过了QQ官方认证)

  1. # appid 100556005
  2. # redirect_uri http://www.corho.com:8080/#/login/callback

在申请QQ登录功能成功之后,我们就可以使用qq提供的SDK工具来完成登录相关功能。

普通项目中使用

  1. <script src="http://connect.qq.com/qc_jssdk.js" data-appid="100556005" data-redirecturi="http://www.corho.com:8080/#/login/callback"></script>
  2. <span id="qqLoginBtn"></span>
  3. <script>
  4. QC.Login({
  5. btnId: 'qqLoginBtn'
  6. })
  7. </script>

看页面生成QQ登录按钮,点击后新窗口打开

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

解决点击登录后打开一个新窗口

image.png
通过生成的代码是一个a标签包着一个img 我们只需要把链接整体拿过来 把window.open 之类的删掉 只有a链接的herf跳转
image.png


四: 在vue项目中使用

步骤一: 引入使用

第一步:

  1. public/index.html添加对.js文件的引用
  1. <script src="http://connect.qq.com/qc_jssdk.js" data-appid="100556005" data-redirecturi="http://www.corho.com:8080/#/login/callback"></script>
  1. 注意,上面填写的data-appidredirecturi是本项目申请的测试账号和密码。

第二步:

  1. 在业务组件中使用
  1. import QC from 'qc' // 注意,我们并没有通过npm i qc 去安装包。而是在index.html中引入的
  1. 在模板中准备一个span(后边会删除它)
  1. <span id="qqLoginBtn"></span>
  1. 调用QC.Login,生成按钮
  1. onMounted(() => {
  2. // 组件渲染完毕,使用QC生成QQ登录按钮
  3. QC.Login({
  4. btnId: 'qqLoginBtn'
  5. })
  6. })

第三步:

  1. vue.config.js添加
  1. // 这个是设置外部扩展,模块为qc变量名为QC,导入qc将不做打包。
  2. configureWebpack: {
  3. externals: {
  4. qc: 'QC'
  5. }
  6. },

告诉wepback,QC 是外部拓展:

  1. 如果遇到 import ‘qc’ 不要去node_modules下找了。
  2. npm run build时,也不要去打包 qc。

自动生成的代码转换a链接跳转

  1. 拿到调试工具中的a链接后把上面写的全删了
  1. <a href="https://graph.qq.com/oauth2.0/authorize?client_id=100556005&amp;response_type=token&amp;scope=all&amp;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>

步骤二: 将测试地址映射到本地

存在的问题:

  1. 登录之后的回调页面是[http://www.corho.com:8080/#/login/callback](http://www.corho.com:8080/#/login/callback) 这个地址不是localhost打头的<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21762447/1627655687323-d1b06ee4-db10-4afe-8704-175a478d4de5.png#clientId=u47fab608-6f0b-4&from=paste&height=258&id=ucde693a1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=515&originWidth=1640&originalType=binary&ratio=1&size=88266&status=done&style=none&taskId=uc6550e3e-48f8-44f0-b831-b0401f436d0&width=820)

目标:

  1. 让[http://www.corho.com:8080](http://www.corho.com:8080) <---->[http://localhost:8080](http://localhost:8080)

第一步: 修改host

  1. 由于域名是[www.corho.com](https://blog.csdn.net/m0_46846526/article/details/www.corho.com)和localhost不一致无法回调页面,需要在本地修改hosts地址。<br /> <br /> windows版本
  1. 1. 找到 C:\Windows\System32\drivers\etc hosts文件
  2. 2. 在文件末尾中加入一行 127.0.0.1 www.corho.com
  3. 3. 保存即可。
  4. # 如果提示没有权限
  5. 1. hosts文件移到桌面,然后进行修改,确认保存。
  6. 2. 将桌面hosts文件替换c盘文件
  1. mac OS
  1. 1. 打开命令行窗口
  2. 2. 输入:sudo vim /etc/hosts
  3. 3. 按下:i
  4. 4. 输入:127.0.0.1 www.corho.com
  5. 5. 按下:esc
  6. 6. 按下:shift + :
  7. 7. 输入:wq 回车即可

第二步: 需要开启webpack服务器权限

  1. vue.config.js中,补充
  1. // 这个是给webpack-dev-server开启可IP和域名访问权限。
  2. chainWebpack: config => {
  3. config.devServer.disableHostCheck(true)
  4. }

重启后,这时候再去尝试登录后就会调到 本地地址 但是本地地址没有设置路由 所以显示的是空页面

配置路由

  1. { path: '/login/callback', component: () => import('@/views/login/callback.vue') }

步骤三: 登录后的三条路线

  • 已注册,已绑定 —-> 登录成功,跳转首页,或者来源页面
  • 已注册,未绑定,绑定手机号 ——> 登录成功,跳转首页,或者来源页面
  • 未注册,补充完善账户信息 ——->登录成功,跳转首页,或者来源页面

背景知识:

  1. 检查是否登录
  1. QC.Login.check() :返回true|false, 用来检查是否登录
  1. 获取登录凭证Id
  1. QC.Login.getMe(unionId=>{console.log(unionId)})
  1. 获取qq信息(头像 昵称)
  1. QC.api('get_user_info').success(res=>console.log(res))

第一条路线:

拿qq返回的unionId调用本地接口
成功:返回的信息是用户信息,调用vuex中的actions保存信息 保存完之后跳转到主页
失败:在catch中原地不动 等待其他两条路线

  1. <script>
  2. import LoginHeader from './components/loginHeader.vue'
  3. import LoginFooter from './components/loginFooter.vue'
  4. import { ref } from 'vue'
  5. import { userQQLogin } from '@/api/user'
  6. import { useStore } from 'vuex'
  7. import { useRouter } from 'vue-router'
  8. import Message from '@/components/XtxMessage.vue'
  9. import QC from 'qc'
  10. import CallbackBind from './components/callbackBind.vue'
  11. import CallbackPatch from './components/callbackPatch.vue'
  12. export default {
  13. name: 'PageCallback',
  14. components: { LoginHeader, LoginFooter, CallbackBind, CallbackPatch },
  15. setup () {
  16. const unionId = ref(null)
  17. const store = useStore()
  18. const router = useRouter()
  19. QC.Login.check() && QC.Login.getMe(openId => {
  20. console.log(openId)
  21. unionId.value = openId
  22. userQQLogin(openId).then(data => {
  23. // 走到then说明成功
  24. // 1. 存储用户信息
  25. store.commit('user/setUser', data.result)
  26. // 跳转到主页
  27. router.push('/')
  28. // 弹框提示
  29. console.log(data)
  30. Message({ type: 'success', text: '登陆成功!' })
  31. }).catch(() => {
  32. // 走到catch说明失败 就留在此页面
  33. console.log('没有绑定 留此页面')
  34. })
  35. })
  36. const hasAccount = ref(true)
  37. return { hasAccount, unionId }
  38. }
  39. }
  40. </script>

第二条路线:

已有账号 请绑定手机 :意思是已有当前项目的账号了 直接绑定手机号

image.png
路线:
获取验证码 : 1. 判断手机号校验是否正确 2. 调用获取验证码接口
调用绑定接口: 1. 看用户名和验证码 表单校验是否正确 2.调用接口 3.提示文本 4.跳到主页

  1. <script>
  2. import { ref, reactive } from 'vue'
  3. import QC from 'qc'
  4. import { Form, Field } from 'vee-validate'
  5. import { mobile, code } from '@/utils/validate'
  6. import { useCountDown } from '@/compositions/index'
  7. import { userQQBindCode } from '@/api/user'
  8. import Message from '@/components/message'
  9. import { useRouter } from 'vue-router'
  10. import { useStore } from 'vuex'
  11. export default {
  12. name: 'CallbackBind',
  13. components: {
  14. Form, Field
  15. },
  16. props: {
  17. unionId: {
  18. type: String,
  19. default: ''
  20. }
  21. },
  22. setup (props) {
  23. // 1. 定义数据项:qq头像 昵称
  24. const nickname = ref('')
  25. const avatar = ref('')
  26. QC.Login.check() && QC.api('get_user_info').success(res => {
  27. avatar.value = res.data.figureurl_2
  28. nickname.value = res.data.nickname
  29. }) // 获取当前登录的QQ账号的信息
  30. const target = ref(null)
  31. // 表单数据对象
  32. const formData = reactive({
  33. mobile: '13241051259',
  34. code: ''
  35. })
  36. // 校验规则
  37. const mySchema = {
  38. mobile: mobile,
  39. code: code
  40. }
  41. const store = useStore()
  42. const router = useRouter()
  43. const { start, time } = useCountDown()
  44. // 发送验证码倒计时
  45. const send = async () => {
  46. // 如果手机号格式不正确
  47. if (mobile(formData.mobile) !== true) {
  48. Message({ type: 'error', text: '手机号格式错误' })
  49. return
  50. }
  51. if (time >= 0) return
  52. try {
  53. await userQQBindCode(formData.mobile).then((res) => {
  54. Message({ type: 'success', text: '获取验证码成功!' })
  55. start(60)
  56. })
  57. } catch (error) {
  58. Message({ type: 'warn', text: error.response.data.message + ', 请稍后重试' || '获取验证码失败!' })
  59. }
  60. }
  61. // 开始绑定
  62. const binding = () => {
  63. console.log(props.unionId)
  64. target.value.validate().then((vilid) => {
  65. // 开始绑定
  66. if (vilid) doBingding()
  67. }
  68. ).catch(vlida => console.log(vlida))
  69. }
  70. const doBingding = async () => {
  71. try {
  72. await store.dispatch('user/userQQBindLogin', { unionId: props.unionId, mobile: formData.mobile, code: formData.code })
  73. Message({ type: 'success', text: '提交成功!' })
  74. router.push('/')
  75. } catch (error) {
  76. console.dir(error)
  77. Message({ type: 'error', text: error.message || '提交失败' })
  78. }
  79. }
  80. return { nickname, avatar, formData, target, mySchema, send, time, binding }
  81. }
  82. }
  83. </script>


第三条路线:

  1. 没有项目账号,也没有绑定手机号<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21762447/1627656492462-3dcc6e7f-eeb7-4661-a464-ddd48bbf4120.png#clientId=u47fab608-6f0b-4&from=paste&height=408&id=ubad2a2b8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=815&originWidth=1094&originalType=binary&ratio=1&size=91890&status=done&style=none&taskId=u242d0f45-565a-4ee0-ace7-46c65f51b68&width=547)<br />路线:<br />首先校验用户名 调用接口 看当前用户名是否存在 <br />校验密码 两次密码是否相等<br />获取验证码 : 1. 判断手机号校验是否正确 2. 调用获取验证码接口 <br />调用绑定接口: 1. 所有表单校验是否正确 2.调用接口 3.提示文本 4.跳到主页
  1. <script>
  2. import { reactive, ref } from 'vue'
  3. import { Form, Field } from 'vee-validate'
  4. import { mobile, code, rePassword, password, accountApi as account } from '@/utils/validate'
  5. import { useCountDown } from '@/compositions/index'
  6. import Message from '@/components/message'
  7. import { userQQPatchCode } from '@/api/user'
  8. import { useRouter } from 'vue-router'
  9. import { useStore } from 'vuex'
  10. export default {
  11. name: 'CallbackPatch',
  12. components: {
  13. Form, Field
  14. },
  15. props: {
  16. unionId: {
  17. type: String,
  18. default: ''
  19. }
  20. },
  21. setup (props) {
  22. // 1. 表单校验 多两个校验:用户名是否存在,再次输入密码是否一致
  23. // 2. 发送短信验证码:接口API定义
  24. // 3. 完善信息
  25. // 表单数据对象
  26. const formData = reactive({
  27. account: null,
  28. mobile: null,
  29. code: null,
  30. password: null,
  31. rePassword: null
  32. })
  33. // 校验表单
  34. const mySchema = {
  35. account,
  36. mobile,
  37. code,
  38. password,
  39. rePassword
  40. }
  41. const { start, time } = useCountDown()
  42. // 发送验证码倒计时
  43. const send = async () => {
  44. // 如果手机号格式不正确
  45. if (mobile(formData.mobile) !== true) {
  46. Message({ type: 'error', text: '手机号格式错误' })
  47. return
  48. }
  49. if (time >= 0) return
  50. try {
  51. await userQQPatchCode(formData.mobile).then((res) => {
  52. Message({ type: 'success', text: '获取验证码成功!' })
  53. start(60)
  54. })
  55. } catch (error) {
  56. Message({ type: 'warn', text: error.response.data.message + ', 请稍后重试' || '获取验证码失败!' })
  57. }
  58. }
  59. // 开始绑定
  60. const target = ref(null)
  61. const store = useStore()
  62. const router = useRouter()
  63. // 立即提交
  64. const submit = () => {
  65. console.log(props.openId)
  66. target.value.validate().then((vilid) => {
  67. // 开始绑定
  68. if (vilid) doSubmit()
  69. }
  70. ).catch(vlida => console.log(vlida))
  71. }
  72. const doSubmit = async () => {
  73. try {
  74. await store.dispatch('user/userQQPatchLogin', {
  75. unionId: props.unionId,
  76. mobile: formData.mobile,
  77. code: formData.code,
  78. account: formData.account,
  79. password: formData.password
  80. })
  81. Message({ type: 'success', text: '提交成功!' })
  82. router.push('/')
  83. } catch (error) {
  84. console.dir(error)
  85. Message({ type: 'error', text: error.message || '提交失败' })
  86. }
  87. }
  88. return { formData, mySchema, send, time, submit, target }
  89. }
  90. }
  91. </script>