Browse Source

admin登录 需要验证码

刘琳琳 2 days ago
parent
commit
b37326d924

+ 42 - 34
src/api/user.js

@@ -1,72 +1,80 @@
-import request from "@/utils/request";
+import request from '@/utils/request'
 // 用户登录
 export function login(data) {
   return request({
-    url: "/api/v1/account/login",
-    method: "get",
-    params: data,
-  });
+    url: '/api/v1/account/login',
+    method: 'get',
+    params: data
+  })
+}
+
+export function getCode(data) {
+  return request({
+    url: '/api/v1/account/getCode',
+    method: 'post',
+    data
+  })
 }
 
 export function getInfo() {
   return request({
-    url: "/api/v1/account/login/info/get",
-    method: "post",
-  });
+    url: '/api/v1/account/login/info/get',
+    method: 'post'
+  })
 }
 // --------------系统配置----------------
 // 查询应用类型列表
 export function appTypeList(data) {
   return request({
-    url: "/api/v1/sys/app/type/list",
-    method: "post",
-    data: data,
-  });
+    url: '/api/v1/sys/app/type/list',
+    method: 'post',
+    data: data
+  })
 }
 // 查询安装任务状态列表
 export function statusList(data) {
   return request({
-    url: "/api/v1/sys/task/status/list",
-    method: "post",
-    data: data,
-  });
+    url: '/api/v1/sys/task/status/list',
+    method: 'post',
+    data: data
+  })
 }
 // 查询升级类型列表
 export function upgradeTypeList(data) {
   return request({
-    url: "/api/v1/sys/upgrade/type/list",
-    method: "post",
-    data: data,
-  });
+    url: '/api/v1/sys/upgrade/type/list',
+    method: 'post',
+    data: data
+  })
 }
 // 添加-编辑账号
 export function accountAdd(data) {
   return request({
-    url: "/api/v1/account/add",
-    method: "post",
-    data: data,
-  });
+    url: '/api/v1/account/add',
+    method: 'post',
+    data: data
+  })
 }
 // 删除账号
 export function accountDel(id) {
   return request({
     url: `/api/v1/account/del/${id}`,
-    method: "post",
-  });
+    method: 'post'
+  })
 }
 // 查询账号列表
 export function accountList(data) {
   return request({
-    url: "/api/v1/account/list",
-    method: "post",
-    data: data,
-  });
+    url: '/api/v1/account/list',
+    method: 'post',
+    data: data
+  })
 }
 // 查询账号分页列表
 export function accountPage(data) {
   return request({
-    url: "/api/v1/account/page",
-    method: "post",
-    data: data,
-  });
+    url: '/api/v1/account/page',
+    method: 'post',
+    data: data
+  })
 }

+ 70 - 69
src/store/modules/user.js

@@ -1,76 +1,77 @@
-import { login, getInfo } from "@/api/user";
-import { getToken, setToken, removeToken } from "@/utils/auth";
-import { resetRouter } from "@/router";
-import { isLogOut, notLogOut } from "@/utils/waitLogOut";
+import { login, getInfo } from '@/api/user'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+import { resetRouter } from '@/router'
+import { isLogOut, notLogOut } from '@/utils/waitLogOut'
 
 const getDefaultState = () => {
   return {
     token: getToken(),
-    username: "",
-    nickname: "",
-    role: "",
-    adminId: "",
-    company: "",
-    WebSocketData: null,
-  };
-};
+    username: '',
+    nickname: '',
+    role: '',
+    adminId: '',
+    company: '',
+    WebSocketData: null
+  }
+}
 
-const state = getDefaultState();
+const state = getDefaultState()
 
 const mutations = {
   RESET_STATE: (state) => {
-    Object.assign(state, getDefaultState());
+    Object.assign(state, getDefaultState())
   },
   SET_TOKEN: (state, token) => {
-    state.token = token;
+    state.token = token
   },
   SET_USERNAME: (state, username) => {
-    state.username = username;
+    state.username = username
   },
   SET_NICKNAME: (state, nickname) => {
-    state.nickname = nickname;
+    state.nickname = nickname
   },
   SET_ROLE: (state, role) => {
-    state.role = role;
+    state.role = role
   },
   SET_ADMINID: (state, adminId) => {
-    state.adminId = adminId;
+    state.adminId = adminId
   },
-  SET_COMPANY: (state, company) => {
-    state.company = company;
+  SET_PHONE: (state, phone) => {
+    state.phone = phone
   },
   SET_COMPANY: (state, company) => {
-    state.company = company;
+    state.company = company
   },
   SET_WEBSOCKETDATA: (state, WebSocketData) => {
-    state.WebSocketData = WebSocketData;
-  },
-};
+    state.WebSocketData = WebSocketData
+  }
+}
 
 const actions = {
   // user login
   login({ commit }, userInfo) {
-    const { username, password } = userInfo;
+    const { username, password, phone, code } = userInfo
     return new Promise((resolve, reject) => {
-      login({ username: username.trim(), password: password })
+      login({ username: username.trim(), password: password, phone, code })
         .then((response) => {
           // 标记登录,开始检测长时间未操作情况
-          notLogOut();
+          notLogOut()
 
-          const { data } = response;
-          commit("SET_TOKEN", data.token);
-          commit("SET_ROLE", data.type);
-          commit("SET_USERNAME", data.username);
-          commit("SET_NICKNAME", data.nickname);
-          commit("SET_ADMINID", data.accountId);
-          commit("SET_COMPANY", data.company);
-          setToken(data.token);
-          resolve();
+          const { data } = response
+          commit('SET_TOKEN', data.token)
+          commit('SET_PHONE', phone?.trim())
+          commit('SET_ROLE', data.type)
+          commit('SET_USERNAME', data.username)
+          commit('SET_NICKNAME', data.nickname)
+          commit('SET_ADMINID', data.accountId)
+          commit('SET_COMPANY', data.company)
+          setToken(data.token)
+          resolve()
         })
         .catch((error) => {
-          reject(error);
-        });
-    });
+          reject(error)
+        })
+    })
   },
 
   // get user info
@@ -78,59 +79,59 @@ const actions = {
     return new Promise((resolve, reject) => {
       getInfo(state.token)
         .then((response) => {
-          const { data } = response;
+          const { data } = response
           if (!data) {
-            return reject("Verification failed, please Login again.");
+            return reject('Verification failed, please Login again.')
           }
-          commit("SET_ROLE", data.type);
-          commit("SET_USERNAME", data.username);
-          commit("SET_NICKNAME", data.nickname);
-          commit("SET_ADMINID", data.id);
-          commit("SET_COMPANY", data.company);
-          resolve(data);
+          commit('SET_ROLE', data.type)
+          commit('SET_USERNAME', data.username)
+          commit('SET_NICKNAME', data.nickname)
+          commit('SET_ADMINID', data.id)
+          commit('SET_COMPANY', data.company)
+          resolve(data)
         })
         .catch((error) => {
-          reject(error);
-        });
-    });
+          reject(error)
+        })
+    })
   },
   // get user info
-  getWebSocket({ commit,state }, WebSocketData) {
+  getWebSocket({ commit, state }, WebSocketData) {
     return new Promise((resolve, reject) => {
-      commit("SET_WEBSOCKETDATA", WebSocketData);
-      resolve(state);
-    });
+      commit('SET_WEBSOCKETDATA', WebSocketData)
+      resolve(state)
+    })
   },
   // user logout
   logout({ commit, state }) {
     return new Promise((resolve, reject) => {
       // 标记已经退出,不用再检测长时间未操作情况
-      isLogOut();
+      isLogOut()
 
       // logout(state.token).then(() => {
-      removeToken(); // must remove  token  first
-      resetRouter();
-      commit("RESET_STATE");
-      resolve();
+      removeToken() // must remove  token  first
+      resetRouter()
+      commit('RESET_STATE')
+      resolve()
       // }).catch(error => {
       //   reject(error)
       // })
-    });
+    })
   },
 
   // remove token
   resetToken({ commit }) {
     return new Promise((resolve) => {
-      removeToken(); // must remove  token  first
-      commit("RESET_STATE");
-      resolve();
-    });
-  },
-};
+      removeToken() // must remove  token  first
+      commit('RESET_STATE')
+      resolve()
+    })
+  }
+}
 
 export default {
   namespaced: true,
   state,
   mutations,
-  actions,
-};
+  actions
+}

+ 39 - 0
src/styles/iconfont/iconfont.css

@@ -0,0 +1,39 @@
+@font-face {
+  font-family: "iconfont"; /* Project id 4803281 */
+  src: url('iconfont.woff2?t=1736127768797') format('woff2'),
+       url('iconfont.woff?t=1736127768797') format('woff'),
+       url('iconfont.ttf?t=1736127768797') format('truetype');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-eye:before {
+  content: "\e62a";
+}
+
+.icon-eye-open:before {
+  content: "\e62b";
+}
+
+.icon-password:before {
+  content: "\e626";
+}
+
+.icon-user:before {
+  content: "\e627";
+}
+
+.icon-phone:before {
+  content: "\e628";
+}
+
+.icon-captcha:before {
+  content: "\e629";
+}
+

File diff suppressed because it is too large
+ 0 - 0
src/styles/iconfont/iconfont.js


+ 51 - 0
src/styles/iconfont/iconfont.json

@@ -0,0 +1,51 @@
+{
+  "id": "4803281",
+  "name": "alipay-auth-platform-admin-icon",
+  "font_family": "iconfont",
+  "css_prefix_text": "icon-",
+  "description": "alipay-auth-platform-admin(航汇-支付宝授权平台)",
+  "glyphs": [
+    {
+      "icon_id": "43044158",
+      "name": "eye",
+      "font_class": "eye",
+      "unicode": "e62a",
+      "unicode_decimal": 58922
+    },
+    {
+      "icon_id": "43044159",
+      "name": "eye-open",
+      "font_class": "eye-open",
+      "unicode": "e62b",
+      "unicode_decimal": 58923
+    },
+    {
+      "icon_id": "43044016",
+      "name": "password",
+      "font_class": "password",
+      "unicode": "e626",
+      "unicode_decimal": 58918
+    },
+    {
+      "icon_id": "43044017",
+      "name": "user",
+      "font_class": "user",
+      "unicode": "e627",
+      "unicode_decimal": 58919
+    },
+    {
+      "icon_id": "43044018",
+      "name": "phone",
+      "font_class": "phone",
+      "unicode": "e628",
+      "unicode_decimal": 58920
+    },
+    {
+      "icon_id": "43044019",
+      "name": "captcha",
+      "font_class": "captcha",
+      "unicode": "e629",
+      "unicode_decimal": 58921
+    }
+  ]
+}

BIN
src/styles/iconfont/iconfont.ttf


BIN
src/styles/iconfont/iconfont.woff


BIN
src/styles/iconfont/iconfont.woff2


+ 1 - 0
src/styles/index.scss

@@ -3,6 +3,7 @@
 @import './transition.scss';
 @import './element-ui.scss';
 @import './sidebar.scss';
+@import "./iconfont/iconfont.css";
 
 body {
   height: 100%;

+ 186 - 63
src/views/login/index.vue

@@ -1,10 +1,10 @@
 <template>
   <div class="login-container">
-    <img class="bg" src="../../assets/bg1.png" alt="" />
+    <img class="bg" src="../../assets/bg1.png" alt="">
     <!-- <img class="logo" :src="logo" alt="" /> -->
     <el-form
-      :rules="rules"
       ref="loginForm"
+      :rules="rules"
       :model="loginForm"
       class="login-form"
       auto-complete="on"
@@ -15,9 +15,7 @@
       </div>
 
       <el-form-item prop="username">
-        <span class="svg-container">
-          <svg-icon icon-class="user" />
-        </span>
+        <i class="iconfont icon-user form-left-container" />
         <el-input
           ref="username"
           v-model="loginForm.username"
@@ -30,9 +28,7 @@
       </el-form-item>
 
       <el-form-item prop="password">
-        <span class="svg-container">
-          <svg-icon icon-class="password" />
-        </span>
+        <i class="iconfont icon-password form-left-container" />
         <el-input
           :key="passwordType"
           ref="password"
@@ -44,109 +40,198 @@
           auto-complete="on"
           @keyup.enter.native="handleLogin"
         />
-        <span class="show-pwd" @click="showPwd">
-          <svg-icon
-            :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
-          />
-        </span>
+        <i :class="`iconfont icon-${passwordType === 'password' ? 'eye' : 'eye-open'} form-right-container`" @click="showPwd" />
       </el-form-item>
+      <template v-if="isAdmin">
+        <el-form-item prop="phone">
+          <i class="iconfont icon-phone form-left-container" />
+          <el-input
+            ref="phone"
+            v-model="loginForm.phone"
+            placeholder="手机号"
+            name="phone"
+            type="text"
+            tabindex="1"
+            auto-complete="on"
+          />
+        </el-form-item>
+
+        <div style="display: flex;align-items: center">
+          <el-form-item prop="code" style="flex: 1">
+            <i class="iconfont icon-captcha form-left-container" />
+            <el-input
+              ref="code"
+              v-model="loginForm.code"
+              placeholder="验证码"
+              name="code"
+              type="text"
+              tabindex="1"
+              auto-complete="on"
+            />
+          </el-form-item>
+          <el-button class="code_btn" type="primary" :disabled="isSending" :loading="submitLoading" @click="sendCode">
+            {{ isSending ? `${countdown}s后重试` : "发送验证码" }}
+          </el-button>
+        </div>
+      </template>
 
       <el-button
         :loading="loading"
         type="primary"
         style="width: 100%; margin-top: 10px; background: #3d22d7"
         @click.native.prevent="handleLogin"
-        >登录</el-button
-      >
+      >登录</el-button>
     </el-form>
   </div>
 </template>
 
 <script>
-import { validUsername } from "@/utils/validate";
-const path = require("path");
+import { validUsername } from '@/utils/validate'
+const path = require('path')
+import { getCode } from '@/api/user'
 
 export default {
-  name: "Login",
+  name: 'Login',
   data() {
-    const validateUsername = (rule, value, callback) => {
-      if (!validUsername(value)) {
-        callback(new Error("Please enter the correct user name"));
+    const validatePhone = (rule, value, callback) => {
+      const phoneReg = /^1[3-9]\d{9}$/
+      if (!value || !phoneReg.test(value)) {
+        callback(new Error('请输入正确的手机号码'))
       } else {
-        callback();
+        callback()
       }
-    };
-    const validatePassword = (rule, value, callback) => {
-      if (value.length < 6) {
-        callback(new Error("The password can not be less than 6 digits"));
-      } else {
-        callback();
-      }
-    };
+    }
+
     return {
       loginForm: {
-        username: "",
-        password: "",
+        username: '',
+        password: '',
+        phone: null,
+        code: ''
       },
       rules: {
+        username: [{
+          required: true,
+          whitespace: true,
+          message: '请输入你的账号',
+          trigger: ['change', 'blur'] }],
         password: [
-          { required: true, message: "请输入密码", trigger: "blur" },
+          { required: true, message: '请输入密码', trigger: 'blur' },
           {
             pattern:
               /^(((?=.*\d)(?=.*[a-z])(?=.*[A-Z]))|((?=.*\d)(?=.*[a-z])(?=.*[~!@#$%^&*]))|((?=.*\d)(?=.*[A-Z])(?=.*[~!@#$%^&*]))|((?=.*[a-z])(?=.*[A-Z])(?=.*[~!@#$%^&*]))|((?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[~!@#$%^&*]))).{10,32}$/,
             message:
-              "必须大小写字母、数字、特殊字符,4种组合中至少满足3种,且密码长度在10-32位之间。若无法登录,请联系管理员!",
-          },
+              '必须大小写字母、数字、特殊字符,4种组合中至少满足3种,且密码长度在10-32位之间。若无法登录,请联系管理员!'
+          }
         ],
+        phone: [{ required: true, trigger: ['change', 'blur'], validator: validatePhone }],
+        code: [{
+          required: true,
+          whitespace: true,
+          message: '请输入验证码',
+          trigger: ['change', 'blur'] }]
       },
       loading: false,
-      passwordType: "password",
+      passwordType: 'password',
       redirect: undefined,
-      title: "OTA中台",
-    };
+      title: 'OTA中台',
+
+      /** 验证码开始 **/
+      isSending: false,
+      submitLoading: false,
+      countdown: 60,
+      timer: null
+      /** 验证码结束 **/
+    }
+  },
+  computed: {
+    isAdmin() {
+      return this.loginForm.username == 'admin'
+    }
   },
   watch: {
     $route: {
-      handler: function (route) {
-        this.redirect = route.query && route.query.redirect;
+      handler: function(route) {
+        this.redirect = route.query && route.query.redirect
       },
-      immediate: true,
-    },
+      immediate: true
+    }
   },
   created() {},
   methods: {
     showPwd() {
-      if (this.passwordType === "password") {
-        this.passwordType = "";
+      if (this.passwordType === 'password') {
+        this.passwordType = ''
       } else {
-        this.passwordType = "password";
+        this.passwordType = 'password'
       }
       this.$nextTick(() => {
-        this.$refs.password.focus();
-      });
+        this.$refs.password.focus()
+      })
     },
     handleLogin() {
       // this.$router.push({ path: "/index" });
       // return
-      this.$refs.loginForm.validate(async (valid) => {
+      this.$refs.loginForm.validate(async(valid) => {
         if (valid) {
-          this.loading = true;
+          this.loading = true
           try {
-            await this.$store.dispatch("user/login", this.loginForm);
-            this.$router.push({ path: "/index" });
-            this.loading = false;
+            if (!this.isAdmin) {
+              this.loginForm.phone = ''
+              this.loginForm.code = ''
+            }
+            await this.$store.dispatch('user/login', this.loginForm)
+            this.$router.push({ path: '/index' })
+            this.loading = false
           } catch (e) {
-            await this.$store.dispatch("user/resetToken");
-            this.loading = false;
+            await this.$store.dispatch('user/resetToken')
+            this.loading = false
           }
         } else {
-          console.log("error submit!!");
-          return false;
+          console.log('error submit!!')
+          return false
         }
-      });
+      })
     },
-  },
-};
+    // 发送验证码
+    sendCode() {
+      this.$refs.loginForm.validateField(['phone'], (valid) => {
+        console.log('发送验证码校验', valid)
+        // 字段校验成功则返回空串
+        if (!valid) {
+          const _this = this
+          _this.submitLoading = true
+          getCode({
+            username: _this.loginForm.username,
+            phone: _this.loginForm.phone
+          }).then(res => {
+            this.$message({
+              type: 'success',
+              message: '发送验证码成功!'
+            })
+            _this.isSending = true
+            _this.countdown = 60
+            // 模拟请求后端发送验证码
+            console.log('验证码已发送至手机号:', _this.loginForm.phone)
+
+            _this.timer = setInterval(() => {
+              if (_this.countdown > 0) {
+                _this.countdown -= 1
+              } else {
+                clearInterval(_this.timer)
+                _this.isSending = false
+              }
+            }, 1000)
+          }).finally(() => {
+            _this.submitLoading = false
+          })
+        } else {
+          return
+        }
+      })
+    }
+  }
+}
 </script>
 
 <style lang="scss">
@@ -167,7 +252,7 @@ $cursor: #fff;
 .login-container {
   .el-input {
     display: inline-block;
-    height: 47px;
+    height: 46px;
     width: 85%;
 
     input {
@@ -177,7 +262,8 @@ $cursor: #fff;
       border-radius: 0px;
       padding: 12px 5px 12px 15px;
       color: black;
-      height: 47px;
+      height: 46px;
+      line-height: 46px;
       caret-color: black;
 
       &:-webkit-autofill {
@@ -192,6 +278,8 @@ $cursor: #fff;
     background: rgba(255, 255, 255, 0.8);
     border-radius: 5px;
     color: #454545;
+    height: 46px;
+    line-height: 46px;
   }
 }
 </style>
@@ -243,12 +331,27 @@ $light_gray: #eee;
     }
   }
 
-  .svg-container {
-    padding: 6px 5px 6px 15px;
+  .form-left-container {
+    height: 46px;
+    width: 30px;
+    text-align: right;
     color: $dark_gray;
     vertical-align: middle;
+    display: inline-block;
+  }
+
+  .form-right-container {
+    height: 46px;
     width: 30px;
+    text-align: center;
+    color: $dark_gray;
+    vertical-align: middle;
     display: inline-block;
+    cursor: pointer;
+  }
+
+  .icon-eye {
+    font-size: 14px;
   }
 
   .title-container {
@@ -273,6 +376,26 @@ $light_gray: #eee;
     user-select: none;
   }
 
+  .code_btn {
+    margin-bottom: 22px;
+    width: 115px;
+    height: 46px;
+    margin-left: 10px;
+    font-size: 14px;
+    border-radius: 4px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    background: #3d22d7;
+    border-color: #3d22d7;
+  }
+
+  .code_btn.is-disabled{
+    background: #6B74C8;
+    border-color: #6B74C8;
+  }
+
   .logo {
     position: absolute;
     right: 50px;

Some files were not shown because too many files changed in this diff