zhoujump hai 1 mes
pai
achega
5f536fde4a

BIN=BIN
public/static/image/icon/google.png


BIN=BIN
public/static/image/icon/linkin.png


BIN=BIN
public/static/image/icon/wechat.png


+ 1 - 1
src/App.vue

@@ -59,7 +59,7 @@ export default {
             })
           }
         } else {
-          if (to.name === 'login' || to.name === 'userRegister' || to.name === 'userForm') {
+          if (to.name === 'login' || to.name === 'userRegister' || to.name === 'userForm' || to.name === 'thirdLogin') {
             next()
           } else {
             next({

+ 103 - 0
src/api/user.js

@@ -117,3 +117,106 @@ export function  confirmEmail(pre_register_key,password,confirm_password) {
     }
   })
 }
+
+/**
+ * 获取微信登录二维码
+ * @returns {*}
+ */
+export function getWechatLogin() {
+  return request({
+    url: '/api/wechat/mp-web-auth',
+    method: 'get',
+    params: {}
+  })
+}
+
+/**
+ * 获取微信登录状态
+ * @param {String} wechatcode getWechatLogin获取的code
+ * @returns {*}
+ */
+export function getWechatStatus(wechatcode) {
+  return request({
+    url: '/api/wechat/mp-web-auth-info',
+    method: 'get',
+    params: {
+      wechatcode
+    }
+  })
+}
+/**
+ * 获取谷歌登录url
+ * @returns {*}
+ */
+export function getGoogleLogin() {
+  return request({
+    url: '/api/google/web-auth-url',
+    method: 'get',
+    params: {}
+  })
+}
+/**
+ * 获取谷歌登录状态
+ * @param {String} state getGoogleLogin获取的state
+ * @param {String} access_token getGoogleLogin获取的access_token
+ * @returns {*}
+ */
+export function getGoogleStatus(state, access_token) {
+  return request({
+    url: '/api/google/web-auth-info',
+    method: 'get',
+    params: {
+      state,
+      access_token
+    }
+  })
+}
+/**
+ * 获取linkedin登录url
+ * @returns {*}
+ */
+export function getLinkedinLogin() {
+  return request({
+    url: '/api/linkedin/web-auth-url',
+    method: 'get',
+    params: {}
+  })
+}
+/**
+ * 获取linkedin登录状态
+ * @param {String} state getLinkedinLogin获取的state
+ * @param {String} code getLinkedinLogin获取的access_token
+ * @returns {*}
+ */
+export function getLinkedinStatus(state, code) {
+  return request({
+    url: '/api/linkedin/web-auth-info',
+    method: 'get',
+    params: {
+      state,
+      code
+    }
+  })
+}
+/**
+ * 绑定手机号接口
+ * @param phone
+ * @param valid_code
+ * @param country_code
+ * @param login_type
+ * @param third_token
+ * @returns {*}
+ */
+export function bindPhone(phone, valid_code, country_code, login_type, third_token){
+  return request({
+    url: '/api/wechat/user-bind',
+    method: 'post',
+    params: {
+      phone,
+      valid_code,
+      country_code,
+      login_type,
+      third_token
+    }
+  })
+}

+ 1 - 1
src/router/index.js

@@ -206,7 +206,7 @@ export const constantRoutes = [
     ]
   },
   {
-    path: '/login',
+    path: '/login-new',
     name: 'login',
     component: () => import('@/views/login/index'),
     hidden: true

+ 30 - 1
src/store/modules/user.js

@@ -18,6 +18,35 @@ export default {
     }
   },
   actions: {
+    tokenLogin({ commit }, playload) {
+      return new Promise((resolve, reject) => {
+        commit('SET_TOKEN', playload.token)
+        getInfo().then(response => {
+          let isAdmin = false
+          response.data.app_list.forEach(app => {
+            if (app.app_code === 'EXPOREG') {
+              isAdmin = true
+            }
+          })
+          commit('SET_USER', {
+            username: response.data.user_name,
+            nickname: response.data.nick_name,
+            avatar: response.data.avatar,
+            email: response.data.email,
+            phone: response.data.phone,
+            isAdmin: isAdmin
+          })
+          if (!isAdmin) {
+            reject('您没有权限访问!')
+          } else {
+            resolve('登录成功')
+          }
+        }).catch(error => {
+          console.log(error)
+          reject('获取用户信息失败')
+        })
+      })
+    },
     /**
      * 登录
      * @param commit
@@ -29,7 +58,7 @@ export default {
      */
     login({ commit }, payload) {
       return new Promise((resolve, reject) => {
-        login(payload.username, payload.password, payload.login_type, payload.login_portal,payload.phone,payload.vaild_code).then(response => {
+        login(payload.username, payload.password, payload.login_type, payload.login_portal, payload.phone, payload.vaild_code).then(response => {
           commit('SET_TOKEN', response.data.api_token)
           getInfo().then(response => {
             let isAdmin = false

+ 174 - 6
src/views/login/index.vue

@@ -1,9 +1,13 @@
 <script>
 import Vue from 'vue'
 import countryCode from '@/lib/countryCode.json'
-import { sendSmsCode, sentEmailCode, register, confirmEmail } from '@/api/user'
+import thirdLogin from './third-login.vue'
+import { sendSmsCode, sentEmailCode, register, confirmEmail, getGoogleStatus, getLinkedinStatus } from '@/api/user'
 export default Vue.extend({
   name: 'Index',
+  components: {
+    thirdLogin
+  },
   data() {
     return {
       countryCode,
@@ -22,13 +26,58 @@ export default Vue.extend({
       timer: null,
       count: 0,
       pre_register_key: '',
+      third_login: '',
+      is_back: false,
       env: process.env.NODE_ENV
     }
   },
   mounted() {
     this.getSavedAccount()
+    this.parseQuery()
   },
   methods: {
+    parseQuery() {
+      const url = new URL(window.location.href.replace('#', '?'))
+      if (url.searchParams.get('code')) {
+        this.is_back = true
+        this.third_login = 'linkin'
+        getLinkedinStatus(
+          url.searchParams.get('state'),
+          url.searchParams.get('code')
+        ).then(res => {
+          this.tokenLogin(res.data.api_token)
+        }).catch(err => {
+          this.$notify({
+            title: '登录失败',
+            message: '登录失败,请稍后重试。',
+            type: 'error'
+          })
+          this.third_login = ''
+          this.is_back = false
+        })
+      }
+      if (url.searchParams.get('access_token')) {
+        this.is_back = true
+        this.third_login = 'google'
+        getGoogleStatus(
+          url.searchParams.get('state'),
+          url.searchParams.get('access_token')
+        ).then(res => {
+          this.tokenLogin(res.data.api_token)
+        }).catch(err => {
+          this.$notify({
+            title: '登录失败',
+            message: '登录失败,请稍后重试。',
+            type: 'error'
+          })
+          this.third_login = ''
+          this.is_back = false
+        })
+      }
+    },
+    openThirdLogin(type) {
+      this.third_login = type
+    },
     sentSMSCode() {
       if (this.loading || this.timer) {
         return
@@ -98,9 +147,9 @@ export default Vue.extend({
         })
       }
       this.loading = true
-      register(this.email, '', this.code, 1, 0).then(res =>{
+      register(this.email, '', this.code, 1, 0).then(res => {
         this.pre_register_key = res.data.pre_register_key
-        confirmEmail(this.pre_register_key,this.password,this.confirmPassword).then(res => {
+        confirmEmail(this.pre_register_key, this.password, this.confirmPassword).then(res => {
           this.isLogin = true
           this.$notify({
             title: '注册成功',
@@ -149,6 +198,13 @@ export default Vue.extend({
         this.savePassword = true
       }
     },
+    tokenLogin(token) {
+      this.$store.dispatch('tokenLogin', { token: token }).then(res => {
+        this.$router.push('/dashboard')
+      }).catch(err => {
+        this.$message.error(err.message)
+      })
+    },
     login() {
       if (!this.username) {
         this.$message.error('请输入用户名')
@@ -182,7 +238,7 @@ export default Vue.extend({
 
 <template>
   <div class="body">
-    <div class="login-cont">
+    <div :style="{height:third_login?'64vh':'auto'}" class="login-cont">
       <div class="image-left">
         <img class="image" src="/static/image/login.webp" alt="">
         <div class="title">展会服务系统</div>
@@ -224,18 +280,46 @@ export default Vue.extend({
             <el-input v-model="password" prefix-icon="el-icon-unlock" placeholder="请输入密码" show-password class="input" />
             <el-input v-model="confirmPassword" prefix-icon="el-icon-unlock" placeholder="请再次输入密码" show-password class="input" />
             <div class="change-way" @click="isPhone=true">使用手机注册</div>
-            <el-button @click="emailRegister()" type="primary" class="button">
+            <el-button type="primary" class="button" @click="emailRegister()">
               <span>注册</span>
               <span v-if="loading" class="el-icon-loading" />
             </el-button>
           </template>
         </template>
+        <div class="third-login">
+          <div class="third-title">第三方登录/注册:</div>
+          <div class="button-list">
+            <div class="third-button" @click="openThirdLogin('wechat')">
+              <img class="icon" src="/static/image/icon/wechat.png">
+            </div>
+            <div class="third-button" @click="openThirdLogin('google')">
+              <img class="icon" src="/static/image/icon/google.png">
+            </div>
+            <div class="third-button" @click="openThirdLogin('linkin')">
+              <img class="icon" src="/static/image/icon/linkin.png">
+            </div>
+          </div>
+        </div>
+        <div :class="['third-window',third_login?'active':'']">
+          <div class="third-head">
+            <span class="third-title">第三方登录</span>
+            <span class="el-icon-close" @click="openThirdLogin('')" />
+          </div>
+          <div class="third-body">
+            <third-login
+              v-if="third_login"
+              :is_back="is_back"
+              :login_type="third_login"
+              @tokenLogin="tokenLogin"
+            />
+          </div>
+        </div>
       </div>
     </div>
   </div>
 </template>
 
-<style scoped>
+<style scoped lang="scss">
   .body{
     width: 100%;
     height: 100%;
@@ -270,10 +354,94 @@ export default Vue.extend({
         }
       }
       .cont-right{
+        position: relative;
         padding: 40px;
         display: flex;
         flex-direction: column;
         grid-gap: 16px;
+        .third-window{
+          position: absolute;
+          top: 0;
+          right: 0;
+          width: 100%;
+          height: 100%;
+          background-color: white;
+          transition-duration: 300ms;
+          transform: translateX(100%);
+          z-index: 10;
+          display: grid;
+          grid-template-rows: auto 1fr;
+          &.active{
+            transform: translateX(0);
+          }
+          .third-body{
+            position: relative;
+            iframe{
+              border: none;
+              width: 100%;
+              height: 100%;
+            }
+          }
+          .third-head{
+            color: grey;
+            padding: 8px;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            .third-title{
+              padding-left: 8px;
+            }
+            .el-icon-close{
+              border-radius: 8px;
+              width: 32px;
+              height: 32px;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              cursor: pointer;
+              &:hover{
+                background-color: #eeeeee;
+              }
+            }
+          }
+        }
+        .third-login{
+          padding-top: 16px;
+          border-top: 1px solid #ececec;
+          .third-title{
+            font-size: 16px;
+            color: grey;
+            margin-bottom: 16px;
+          }
+          .button-list{
+            display: flex;
+            gap: 16px;
+            .third-button{
+              cursor: pointer;
+              width: 46px;
+              height: 46px;
+              border-radius: 50%;
+              background-color: #eeeeee;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              .icon{
+                transition-duration: 300ms;
+                filter: brightness(0) opacity(0.5);
+                width: 22px;
+                height: 22px;
+                object-fit: contain;
+              }
+              &:hover{
+                background-color: #f4f4f4;
+                .icon{
+                  scale: 1.04;
+                  filter: brightness(1) opacity(1);
+                }
+              }
+            }
+          }
+        }
         .title{
           height: 36px;
           font-weight: bold;

+ 155 - 0
src/views/login/third-login.vue

@@ -0,0 +1,155 @@
+<script>
+import Vue from 'vue'
+import VueQr from 'vue-qr'
+import { getWechatLogin, getWechatStatus, getGoogleLogin, getLinkedinLogin, bindPhone} from '@/api/user'
+export default Vue.extend({
+  name: 'ThirdLogin',
+  components: {
+    VueQr
+  },
+  props: {
+    login_type: {
+      type: String,
+      default: ''
+    },
+    is_back: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      timer: null,
+      wechat_info: ''
+    }
+  },
+  mounted() {
+    this.init()
+  },
+  beforeDestroy() {
+    clearInterval(this.timer)
+  },
+  methods: {
+    init() {
+      if (!this.is_back) {
+        if (this.login_type === 'wechat') {
+          this.initWechatLogin()
+        }
+        if (this.login_type === 'google') {
+          this.initGoogleLogin()
+        }
+        if (this.login_type === 'linkin') {
+          this.initLinkinLogin()
+        }
+      }
+    },
+    initGoogleLogin() {
+      getGoogleLogin().then(res => {
+        const google_info = res.data
+        const url = new URL(google_info.url)
+        url.searchParams.set('redirect_uri', 'http://localhost:9528' + '/login-new')
+        location.href = url
+      })
+    },
+    initLinkinLogin() {
+      getLinkedinLogin().then(res => {
+        console.log(res)
+        const linkedin_info = res.data
+        const url = new URL(linkedin_info.url)
+        url.searchParams.set('redirect_uri', 'http://localhost:9528' + '/login-new')
+        location.href = url
+      })
+    },
+    initWechatLogin() {
+      getWechatLogin().then(res => {
+        this.wechat_info = res.data
+        this.timer = setInterval(() => {
+          getWechatStatus(this.wechat_info.wechatcode).then(status => {
+            if (status.data.api_token) {
+              clearInterval(this.timer)
+              this.$emit('tokenLogin', status.data.api_token)
+            }
+          })
+        }, 3000)
+      })
+    }
+  }
+})
+</script>
+
+<template>
+  <div class="root">
+    <div v-if="login_type === 'wechat'" class="wechat-login">
+      <div class="title">
+        <img class="icon" src="/static/image/icon/wechat.png">
+        <span class="text">微信扫码登录</span>
+      </div>
+      <div class="qr-code">
+        <vue-qr v-show="wechat_info.url" class="vue-qr" :margin="0" :text="wechat_info.url" />
+      </div>
+      <div class="tips">打开微信扫一扫,关注公众号自动登录</div>
+    </div>
+    <div v-if="login_type === 'google'" class="wechat-login">
+      <div class="title">
+        <span class="text">Google登录</span>
+      </div>
+      <img class="icon" src="/static/image/icon/google.png">
+      <div class="tips">正在准备登录信息...</div>
+    </div>
+    <div v-if="login_type === 'linkin'" class="wechat-login">
+      <div class="title">
+        <span class="text">LinkedIn登录</span>
+      </div>
+      <img class="icon" src="/static/image/icon/linkin.png">
+      <div class="tips">正在准备登录信息...</div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+  .root{
+    width: 100%;
+    height: 100%;
+  }
+  .wechat-login{
+    width: 100%;
+    height: 100%;
+    display: flex;
+    gap: 36px;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    .icon{
+      width: 30%;
+      aspect-ratio: 1;
+      object-fit: contain;
+    }
+    .qr-code{
+      background-color: lightgray;
+      width: 42%;
+      aspect-ratio: 1;
+      .vue-qr{
+        width: 100%;
+        height: 100%;
+      }
+    }
+    .title{
+      display: flex;
+      gap: 4px;
+      align-items: center;
+      .text{
+        font-size: 24px;
+        font-weight: bold;
+      }
+      .icon{
+        object-fit: contain;
+        width: 36px;
+        height: 36px;
+      }
+    }
+    .tips{
+      font-size: 16px;
+      color: grey;
+    }
+  }
+</style>

+ 1 - 1
vue.config.js

@@ -14,7 +14,7 @@ const name = defaultSettings.title || 'vue Element Admin' // page title
 // For example, Mac: sudo npm run
 // You can change the port by the following method:
 // port = 9527 npm run dev OR npm run dev --port = 9527
-const port = process.env.port || process.env.npm_config_port || 9090 // dev port
+const port = process.env.port || process.env.npm_config_port || 9528 // dev port
 
 // All configuration item explanations can be find in https://cli.vuejs.org/config/
 module.exports = {