1、对接谷歌验证器
This commit is contained in:
		| @ -64,6 +64,7 @@ | |||||||
|     "normalize.css": "8.0.1", |     "normalize.css": "8.0.1", | ||||||
|     "nprogress": "0.2.0", |     "nprogress": "0.2.0", | ||||||
|     "path-to-regexp": "6.1.0", |     "path-to-regexp": "6.1.0", | ||||||
|  |     "qrcode": "^1.5.4", | ||||||
|     "remixicon": "^2.5.0", |     "remixicon": "^2.5.0", | ||||||
|     "sass-resources-loader": "^2.0.3", |     "sass-resources-loader": "^2.0.3", | ||||||
|     "screenfull": "5.0.2", |     "screenfull": "5.0.2", | ||||||
|  | |||||||
| @ -53,3 +53,12 @@ export function clearAllAlarmLog() { | |||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | //导出告警记录 | ||||||
|  | export function exportMmAlarmLog(query) { | ||||||
|  |     return request({ | ||||||
|  |         url: '/api/v1/mm-alarm-log/export', | ||||||
|  |         method: 'get', | ||||||
|  |         params: query, | ||||||
|  |         responseType: 'blob', | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,142 +1,178 @@ | |||||||
| import request from '@/utils/request' | import request from "@/utils/request"; | ||||||
|  |  | ||||||
| // 查询用户列表 | // 查询用户列表 | ||||||
| export function listUser(query) { | export function listUser(query) { | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/sys-user', |     url: "/api/v1/sys-user", | ||||||
|     method: 'get', |     method: "get", | ||||||
|     params: query |     params: query, | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| // 查询用户详细 | // 查询用户详细 | ||||||
| export function getUser(userId) { | export function getUser(userId) { | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/sys-user/' + userId, |     url: "/api/v1/sys-user/" + userId, | ||||||
|     method: 'get' |     method: "get", | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function getUserInit() { | export function getUserInit() { | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/sys-user/', |     url: "/api/v1/sys-user/", | ||||||
|     method: 'get' |     method: "get", | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| // 新增用户 | // 新增用户 | ||||||
| export function addUser(data) { | export function addUser(data) { | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/sys-user', |     url: "/api/v1/sys-user", | ||||||
|     method: 'post', |     method: "post", | ||||||
|     data: data |     data: data, | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| // 修改用户 | // 修改用户 | ||||||
| export function updateUser(data) { | export function updateUser(data) { | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/sys-user', |     url: "/api/v1/sys-user", | ||||||
|     method: 'put', |     method: "put", | ||||||
|     data: data |     data: data, | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| // 删除用户 | // 删除用户 | ||||||
| export function delUser(data) { | export function delUser(data) { | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/sys-user', |     url: "/api/v1/sys-user", | ||||||
|     method: 'delete', |     method: "delete", | ||||||
|     data: data |     data: data, | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| // 导出用户 | // 导出用户 | ||||||
| export function exportUser(query) { | export function exportUser(query) { | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/sys-user/export', |     url: "/api/v1/sys-user/export", | ||||||
|     method: 'get', |     method: "get", | ||||||
|     params: query |     params: query, | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| // 用户密码重置 | // 用户密码重置 | ||||||
| export function resetUserPwd(userId, password) { | export function resetUserPwd(userId, password) { | ||||||
|   const data = { |   const data = { | ||||||
|     userId, |     userId, | ||||||
|     password |     password, | ||||||
|   } |   }; | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/user/pwd/reset', |     url: "/api/v1/user/pwd/reset", | ||||||
|     method: 'put', |     method: "put", | ||||||
|     data: data |     data: data, | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| // 用户状态修改 | // 用户状态修改 | ||||||
| export function changeUserStatus(e) { | export function changeUserStatus(e) { | ||||||
|   const data = { |   const data = { | ||||||
|     userId: e.userId, |     userId: e.userId, | ||||||
|     status: e.status |     status: e.status, | ||||||
|   } |   }; | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/user/status', |     url: "/api/v1/user/status", | ||||||
|     method: 'put', |     method: "put", | ||||||
|     data: data |     data: data, | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| // 修改用户个人信息 | // 修改用户个人信息 | ||||||
| export function updateUserProfile(data) { | export function updateUserProfile(data) { | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/sys-user/profile', |     url: "/api/v1/sys-user/profile", | ||||||
|     method: 'put', |     method: "put", | ||||||
|     data: data |     data: data, | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| // 下载用户导入模板 | // 下载用户导入模板 | ||||||
| export function importTemplate() { | export function importTemplate() { | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/sys-user/importTemplate', |     url: "/api/v1/sys-user/importTemplate", | ||||||
|     method: 'get' |     method: "get", | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| // 用户密码重置 | // 用户密码重置 | ||||||
| export function updateUserPwd(oldPassword, newPassword) { | export function updateUserPwd(oldPassword, newPassword) { | ||||||
|   const data = { |   const data = { | ||||||
|     oldPassword, |     oldPassword, | ||||||
|     newPassword |     newPassword, | ||||||
|   } |   }; | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/user/pwd/set', |     url: "/api/v1/user/pwd/set", | ||||||
|     method: 'put', |     method: "put", | ||||||
|     data: data |     data: data, | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| // 用户头像上传 | // 用户头像上传 | ||||||
| export function uploadAvatar(data) { | export function uploadAvatar(data) { | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/user/avatar', |     url: "/api/v1/user/avatar", | ||||||
|     method: 'post', |     method: "post", | ||||||
|     data: data |     data: data, | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| // 查询用户个人信息 | // 查询用户个人信息 | ||||||
| export function getUserProfile() { | export function getUserProfile() { | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/user/profile', |     url: "/api/v1/user/profile", | ||||||
|     method: 'get' |     method: "get", | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| // 查询用户列表 | // 查询用户列表 | ||||||
| export function getUserOptions() { | export function getUserOptions() { | ||||||
|   return request({ |   return request({ | ||||||
|     url: '/api/v1/sys-user/options', |     url: "/api/v1/sys-user/options", | ||||||
|     method: 'get' |     method: "get", | ||||||
|   }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 检查是否需要谷歌验证 | ||||||
|  | export function needCheckGoogleAuth(headers) { | ||||||
|  |   return request({ | ||||||
|  |     url: "/api/v1/need-check-google-auth", | ||||||
|  |     method: "get", | ||||||
|  |     headers: headers, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //验证google验证码 | ||||||
|  | export function checkGoogleCode(params,headers) { | ||||||
|  |   return request({ | ||||||
|  |     url: "/api/v1/google-code", | ||||||
|  |     method: "get", | ||||||
|  |     params: params, | ||||||
|  |     headers: headers, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //判断是否需要谷歌验证 | ||||||
|  | export function needGoolAuth() { | ||||||
|  |   return request({ | ||||||
|  |     url: "/api/v1/need-auth", | ||||||
|  |     method: "get", | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //绑定谷歌认证器 | ||||||
|  | export function setGoogleAuth(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: "/api/v1/set-google-auth", | ||||||
|  |     method: "put", | ||||||
|  |     data: data, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										149
									
								
								src/layout/SetGoogleSecret.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								src/layout/SetGoogleSecret.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,149 @@ | |||||||
|  | <template> | ||||||
|  |     <el-dialog :visible.sync="dialogVisible" title="绑定谷歌验证器" width="680px" :show-close="false" :close-on-press-escape="false" | ||||||
|  |         :close-on-click-modal="false"  class="google-bind-dialog"> | ||||||
|  |         <div class="dialog-body"> | ||||||
|  |             <p class="tip-text">1. 使用 <strong>Google Authenticator</strong> 扫描二维码,或手动输入密钥添加账号</p> | ||||||
|  |  | ||||||
|  |             <div class="qr-section"> | ||||||
|  |                 <canvas ref="qrCanvas" class="qr-canvas"></canvas> | ||||||
|  |                 <div class="manual-section"> | ||||||
|  |                     <!-- <p><strong>账户:</strong> {{ account }}</p> --> | ||||||
|  |                     <p> | ||||||
|  |                         <strong>密钥:</strong> | ||||||
|  |                         <el-tag size="small" type="success">{{ secret }}</el-tag> | ||||||
|  |                         <el-button type="text" size="mini" @click="copySecret">复制</el-button> | ||||||
|  |                     </p> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <el-input v-model="code" placeholder="请输入 6 位验证码" maxlength="6" class="code-input" | ||||||
|  |                 @keyup.enter.native="submit" ref="codeInput" /> | ||||||
|  |  | ||||||
|  |             <div class="error-text" v-if="errorMsg">{{ errorMsg }}</div> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <template #footer> | ||||||
|  |             <el-button type="primary" :loading="loading" :disabled="code.length !== 6" @click="submit"> | ||||||
|  |                 确认绑定 | ||||||
|  |             </el-button> | ||||||
|  |         </template> | ||||||
|  |     </el-dialog> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | import QRCode from 'qrcode' | ||||||
|  | import { setGoogleAuth } from '@/api/admin/sys-user' | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |     name: 'BindGoogleDialog', | ||||||
|  |     props: { | ||||||
|  |         visible: { type: Boolean, default: true }, | ||||||
|  |         otpAuthUrl: { type: String, default: '' }, | ||||||
|  |         secret: { type: String, default: '' }, | ||||||
|  |         account: { type: String, default: '' }, | ||||||
|  |     }, | ||||||
|  |     data() { | ||||||
|  |         return { | ||||||
|  |             code: '', | ||||||
|  |             loading: false, | ||||||
|  |             errorMsg: '' | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     computed: { | ||||||
|  |         dialogVisible: { | ||||||
|  |             get() { | ||||||
|  |                 return this.visible | ||||||
|  |             }, | ||||||
|  |             set(val) { | ||||||
|  |                 this.$emit('update:visible', val) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     watch: { | ||||||
|  |         visible(val) { | ||||||
|  |             if (val) { | ||||||
|  |                 this.code = '' | ||||||
|  |                 this.errorMsg = '' | ||||||
|  |                 this.$nextTick(() => { | ||||||
|  |                     QRCode.toCanvas(this.$refs.qrCanvas, this.otpAuthUrl, { width: 180 }) | ||||||
|  |                     this.$refs.codeInput.focus() | ||||||
|  |                 }) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     methods: { | ||||||
|  |         submit() { | ||||||
|  |             this.loading = true | ||||||
|  |             setGoogleAuth({ | ||||||
|  |                 secret: this.secret, | ||||||
|  |                 code: this.code | ||||||
|  |             }) | ||||||
|  |                 .then(res => { | ||||||
|  |                     if (res && res.code === 200) { | ||||||
|  |                         this.dialogVisible = false | ||||||
|  |                         this.$message.success('绑定成功') | ||||||
|  |                     } else { | ||||||
|  |                         this.errorMsg = res.message || '绑定失败' | ||||||
|  |                     } | ||||||
|  |                 }) | ||||||
|  |                 .finally(() => { | ||||||
|  |                     this.loading = false | ||||||
|  |                 }) | ||||||
|  |         }, | ||||||
|  |         copySecret() { | ||||||
|  |             navigator.clipboard.writeText(this.secret).then(() => { | ||||||
|  |                 this.$message.success('密钥已复制') | ||||||
|  |             }).catch(() => { | ||||||
|  |                 this.$message.error('复制失败,请手动复制') | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style scoped> | ||||||
|  | .google-bind-dialog ::v-deep .el-dialog { | ||||||
|  |     border-radius: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .dialog-body { | ||||||
|  |     padding: 10px 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tip-text { | ||||||
|  |     font-size: 14px; | ||||||
|  |     color: #666; | ||||||
|  |     margin-bottom: 15px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .qr-section { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: flex-start; | ||||||
|  |     gap: 16px; | ||||||
|  |     margin-bottom: 15px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .qr-canvas { | ||||||
|  |     width: 180px; | ||||||
|  |     height: 180px; | ||||||
|  |     border: 1px solid #eee; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .manual-section { | ||||||
|  |     flex: 1; | ||||||
|  |     font-size: 13px; | ||||||
|  |     color: #333; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .code-input { | ||||||
|  |     width: 240px; | ||||||
|  |     display: block; | ||||||
|  |     margin: 0 auto; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .error-text { | ||||||
|  |     text-align: center; | ||||||
|  |     margin-top: 8px; | ||||||
|  |     color: red; | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @ -13,16 +13,22 @@ | |||||||
|         <settings /> |         <settings /> | ||||||
|       </right-panel> |       </right-panel> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  |     <!-- 绑定谷歌验证码弹窗 --> | ||||||
|  |     <set-google-secret :visible.sync="showSetGooleSecret" :otpAuthUrl="needGoolAuthData.otpAuthUrl" | ||||||
|  |       :secret="needGoolAuthData.secret" @success="handleBindSuccess"></set-google-secret> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import RightPanel from '@/components/RightPanel' | import RightPanel from '@/components/RightPanel' | ||||||
|  | import { needGoolAuth, setGoogleAuth } from '@/api/admin/sys-user' | ||||||
| import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components' | import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components' | ||||||
|  | import SetGoogleSecret from './SetGoogleSecret' | ||||||
| import ResizeMixin from './mixin/ResizeHandler' | import ResizeMixin from './mixin/ResizeHandler' | ||||||
| import { mapState } from 'vuex' | import { mapState } from 'vuex' | ||||||
| import variables from '@/styles/variables.scss' | import variables from '@/styles/variables.scss' | ||||||
| import dingSound from '@/assets/tiktok/sisfus.mp3' | // import dingSound from '@/assets/tiktok/sisfus.mp3' | ||||||
| import checkPermisAction from '@/utils/permisaction' | import checkPermisAction from '@/utils/permisaction' | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
| @ -33,11 +39,14 @@ export default { | |||||||
|     RightPanel, |     RightPanel, | ||||||
|     Settings, |     Settings, | ||||||
|     Sidebar, |     Sidebar, | ||||||
|     TagsView |     TagsView, | ||||||
|  |     SetGoogleSecret | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|       voice: null, |       voice: null, | ||||||
|  |       showSetGooleSecret: false, | ||||||
|  |       needGoolAuthData: {} | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   mixins: [ResizeMixin], |   mixins: [ResizeMixin], | ||||||
| @ -63,35 +72,52 @@ export default { | |||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   created() { |   created() { | ||||||
|     // if (!this.roles.includes('admin')) { |     this.getNeedGoolAuth() | ||||||
|     //   this.currentRole = 'editorDashboard' |  | ||||||
|     // } |  | ||||||
|  |  | ||||||
|     if (checkPermisAction(['admin:mmAlarmLog:notice'])) { |     if (checkPermisAction(['admin:mmAlarmLog:notice'])) { | ||||||
|       this.$confirm('是否接收警告?', '提示', { |       // this.$confirm('是否接收警告?', '提示', { | ||||||
|         distinguishCancelAndClose: true, |       //   distinguishCancelAndClose: true, | ||||||
|         confirmButtonText: '确定', |       //   confirmButtonText: '确定', | ||||||
|         cancelButtonText: '取消', |       //   cancelButtonText: '取消', | ||||||
|         type: 'warning' |       //   type: 'warning' | ||||||
|       }).then(() => { |       // }).then(() => { | ||||||
|         this.voice = new Audio(dingSound) |       //   this.voice = new Audio(dingSound) | ||||||
|  |  | ||||||
|         this.initWebSocket() |       this.initWebSocket() | ||||||
|       }).catch(() => { |       // }).catch(() => { | ||||||
|         console.log('取消') |       //   console.log('取消') | ||||||
|       }); |       // }); | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   destroyed() { |   destroyed() { | ||||||
|     console.log('断开websocket连接') |     console.log('断开websocket连接') | ||||||
|     this.websock.close() // 离开路由之后断开websocket连接 |     this.websock.close() // 离开路由之后断开websocket连接 | ||||||
|     // unWsLogout(this.id, this.group).then(response => { |  | ||||||
|     //   console.log(response.data) |  | ||||||
|     // } |  | ||||||
|     // ) |  | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     checkPermisAction, |     checkPermisAction, | ||||||
|  |     getNeedGoolAuth() { | ||||||
|  |       const loading = this.$loading({ | ||||||
|  |         lock: true, | ||||||
|  |         text: 'Loading', | ||||||
|  |         spinner: 'el-icon-loading', | ||||||
|  |         background: 'rgba(0, 0, 0, 0.7)' | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       needGoolAuth() | ||||||
|  |         .then(response => { | ||||||
|  |           console.log("needGoogleAuth", response) | ||||||
|  |           if (response.code === 200) { | ||||||
|  |             if (response.data.needGooglAuth) { | ||||||
|  |               this.needGoolAuthData.otpAuthUrl = response.data.otpAuthUrl | ||||||
|  |               this.needGoolAuthData.secret = response.data.secret | ||||||
|  |               this.showSetGooleSecret = true | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |         .finally(() => { | ||||||
|  |           loading.close() | ||||||
|  |         }) | ||||||
|  |     }, | ||||||
|     initWebSocket() { // 初始化weosocket |     initWebSocket() { // 初始化weosocket | ||||||
|       const wsuri = `ws://${process.env.VUE_APP_WEBSOCKET_URL}/ws?token=${this.$store.state.user.token}` |       const wsuri = `ws://${process.env.VUE_APP_WEBSOCKET_URL}/ws?token=${this.$store.state.user.token}` | ||||||
|  |  | ||||||
| @ -110,7 +136,6 @@ export default { | |||||||
|       this.initWebSocket() |       this.initWebSocket() | ||||||
|     }, |     }, | ||||||
|     websocketonmessage(e) { // 数据接收 |     websocketonmessage(e) { // 数据接收 | ||||||
|       console.log("ws:", e.data) |  | ||||||
|       try { |       try { | ||||||
|         let data = JSON.parse(e.data) |         let data = JSON.parse(e.data) | ||||||
|  |  | ||||||
| @ -122,7 +147,7 @@ export default { | |||||||
|           duration: 0 |           duration: 0 | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         this.playVoice("钱包告警") |         // this.playVoice("钱包告警") | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         console.log("接收websocket数据失败:", err) |         console.log("接收websocket数据失败:", err) | ||||||
|       } |       } | ||||||
| @ -146,6 +171,9 @@ export default { | |||||||
|     }, |     }, | ||||||
|     handleClickOutside() { |     handleClickOutside() { | ||||||
|       this.$store.dispatch('app/closeSideBar', { withoutAnimation: false }) |       this.$store.dispatch('app/closeSideBar', { withoutAnimation: false }) | ||||||
|  |     }, | ||||||
|  |     handleBindSuccess() { | ||||||
|  |       this.showSetGooleSecret = false | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,153 +1,174 @@ | |||||||
| import { login, logout, getInfo, refreshtoken } from '@/api/user' | import { login, logout, getInfo, refreshtoken } from "@/api/user"; | ||||||
| import { getToken, setToken, removeToken } from '@/utils/auth' | import { getToken, setToken, removeToken } from "@/utils/auth"; | ||||||
| import router, { resetRouter } from '@/router' | import router, { resetRouter } from "@/router"; | ||||||
| import storage from '@/utils/storage' | import storage from "@/utils/storage"; | ||||||
|  |  | ||||||
| const state = { | const state = { | ||||||
|   token: getToken(), |   token: getToken(), | ||||||
|   name: '', |   name: "", | ||||||
|   avatar: '', |   avatar: "", | ||||||
|   introduction: '', |   introduction: "", | ||||||
|   roles: [], |   roles: [], | ||||||
|   permissions: [], |   permissions: [], | ||||||
|   permisaction: [] |   permisaction: [], | ||||||
| } | }; | ||||||
|  |  | ||||||
| const mutations = { | const mutations = { | ||||||
|   SET_TOKEN: (state, token) => { |   SET_TOKEN: (state, token) => { | ||||||
|     state.token = token |     state.token = token; | ||||||
|   }, |   }, | ||||||
|   SET_INTRODUCTION: (state, introduction) => { |   SET_INTRODUCTION: (state, introduction) => { | ||||||
|     state.introduction = introduction |     state.introduction = introduction; | ||||||
|   }, |   }, | ||||||
|   SET_NAME: (state, name) => { |   SET_NAME: (state, name) => { | ||||||
|     state.name = name |     state.name = name; | ||||||
|   }, |   }, | ||||||
|   SET_AVATAR: (state, avatar) => { |   SET_AVATAR: (state, avatar) => { | ||||||
|     if (avatar.indexOf('http') !== -1) { |     if (avatar.indexOf("http") !== -1) { | ||||||
|       state.avatar = avatar |       state.avatar = avatar; | ||||||
|     } else { |     } else { | ||||||
|       state.avatar = process.env.VUE_APP_BASE_API + avatar |       state.avatar = process.env.VUE_APP_BASE_API + avatar; | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   SET_ROLES: (state, roles) => { |   SET_ROLES: (state, roles) => { | ||||||
|     state.roles = roles |     state.roles = roles; | ||||||
|   }, |   }, | ||||||
|   SET_PERMISSIONS: (state, permisaction) => { |   SET_PERMISSIONS: (state, permisaction) => { | ||||||
|     state.permisaction = permisaction |     state.permisaction = permisaction; | ||||||
|   } |   }, | ||||||
| } | }; | ||||||
|  |  | ||||||
| const actions = { | const actions = { | ||||||
|   // user login |   // user login | ||||||
|   login({ commit }, userInfo) { |   login({ commit }, { userInfo, handleResponse }) { | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       login(userInfo).then(response => { |       login(userInfo) | ||||||
|         const { token } = response |         .then(async(res) => { | ||||||
|         commit('SET_TOKEN', token) |           // 自定义回调处理(判断是否需要谷歌验证) | ||||||
|         setToken(token) |           if (handleResponse) { | ||||||
|         resolve() |             const shouldSetToken =await handleResponse(res); | ||||||
|       }).catch(error => { |  | ||||||
|         reject(error) |             console.log("shouldSetToken", shouldSetToken); | ||||||
|       }) |             if (!shouldSetToken) return resolve(); // 不设置 token | ||||||
|     }) |           } | ||||||
|  |  | ||||||
|  |           const { token } = res; | ||||||
|  |           if (token) { | ||||||
|  |             commit("SET_TOKEN", token); | ||||||
|  |             setToken(token); | ||||||
|  |             resolve(res); | ||||||
|  |           } else { | ||||||
|  |             reject(new Error("登录失败:无 token")); | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |         .catch(reject); | ||||||
|  |     }); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   // get user info |   // get user info | ||||||
|   getInfo({ commit, state }) { |   getInfo({ commit, state }) { | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       getInfo().then(response => { |       getInfo() | ||||||
|         if (!response || !response.data) { |         .then((response) => { | ||||||
|           commit('SET_TOKEN', '') |           if (!response || !response.data) { | ||||||
|           removeToken() |             commit("SET_TOKEN", ""); | ||||||
|           resolve() |             removeToken(); | ||||||
|         } |             resolve(); | ||||||
|  |           } | ||||||
|  |  | ||||||
|         const { roles, name, avatar, introduction, permissions } = response.data |           const { roles, name, avatar, introduction, permissions } = | ||||||
|  |             response.data; | ||||||
|  |  | ||||||
|         // roles must be a non-empty array |           // roles must be a non-empty array | ||||||
|         if (!roles || roles.length <= 0) { |           if (!roles || roles.length <= 0) { | ||||||
|           reject('getInfo: roles must be a non-null array!') |             reject("getInfo: roles must be a non-null array!"); | ||||||
|         } |           } | ||||||
|         commit('SET_PERMISSIONS', permissions) |           commit("SET_PERMISSIONS", permissions); | ||||||
|         commit('SET_ROLES', roles) |           commit("SET_ROLES", roles); | ||||||
|         commit('SET_NAME', name) |           commit("SET_NAME", name); | ||||||
|         commit('SET_AVATAR', avatar) |           commit("SET_AVATAR", avatar); | ||||||
|         commit('SET_INTRODUCTION', introduction) |           commit("SET_INTRODUCTION", introduction); | ||||||
|         resolve(response) |           resolve(response); | ||||||
|       }).catch(error => { |         }) | ||||||
|         reject(error) |         .catch((error) => { | ||||||
|       }) |           reject(error); | ||||||
|     }) |         }); | ||||||
|  |     }); | ||||||
|   }, |   }, | ||||||
|   // 退出系统 |   // 退出系统 | ||||||
|   LogOut({ commit, state }) { |   LogOut({ commit, state }) { | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       logout(state.token).then(() => { |       logout(state.token) | ||||||
|         commit('SET_TOKEN', '') |         .then(() => { | ||||||
|         commit('SET_ROLES', []) |           commit("SET_TOKEN", ""); | ||||||
|         commit('SET_PERMISSIONS', []) |           commit("SET_ROLES", []); | ||||||
|         removeToken() |           commit("SET_PERMISSIONS", []); | ||||||
|         storage.clear() |           removeToken(); | ||||||
|         resolve() |           storage.clear(); | ||||||
|       }).catch(error => { |           resolve(); | ||||||
|         reject(error) |         }) | ||||||
|       }) |         .catch((error) => { | ||||||
|     }) |           reject(error); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|   }, |   }, | ||||||
|   // 刷新token |   // 刷新token | ||||||
|   refreshToken({ commit, state }) { |   refreshToken({ commit, state }) { | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       refreshtoken({ token: state.token }).then(response => { |       refreshtoken({ token: state.token }) | ||||||
|         const { token } = response |         .then((response) => { | ||||||
|         commit('SET_TOKEN', token) |           const { token } = response; | ||||||
|         setToken(token) |           commit("SET_TOKEN", token); | ||||||
|         resolve() |           setToken(token); | ||||||
|       }).catch(error => { |           resolve(); | ||||||
|         reject(error) |         }) | ||||||
|       }) |         .catch((error) => { | ||||||
|     }) |           reject(error); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   // remove token |   // remove token | ||||||
|   resetToken({ commit }) { |   resetToken({ commit }) { | ||||||
|     return new Promise(resolve => { |     return new Promise((resolve) => { | ||||||
|       commit('SET_TOKEN', '') |       commit("SET_TOKEN", ""); | ||||||
|       removeToken() |       removeToken(); | ||||||
|       resolve() |       resolve(); | ||||||
|     }) |     }); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   // dynamically modify permissions |   // dynamically modify permissions | ||||||
|   changeRoles({ commit, dispatch }, role) { |   changeRoles({ commit, dispatch }, role) { | ||||||
|     // eslint-disable-next-line no-async-promise-executor |     // eslint-disable-next-line no-async-promise-executor | ||||||
|     return new Promise(async resolve => { |     return new Promise(async (resolve) => { | ||||||
|       const token = role + '-token' |       const token = role + "-token"; | ||||||
|  |  | ||||||
|       commit('SET_TOKEN', token) |       commit("SET_TOKEN", token); | ||||||
|       setToken(token) |       setToken(token); | ||||||
|  |  | ||||||
|       const { roles } = await dispatch('getInfo') |       const { roles } = await dispatch("getInfo"); | ||||||
|  |  | ||||||
|       resetRouter() |       resetRouter(); | ||||||
|  |  | ||||||
|       // generate accessible routes map based on roles |       // generate accessible routes map based on roles | ||||||
|       const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true }) |       const accessRoutes = await dispatch("permission/generateRoutes", roles, { | ||||||
|  |         root: true, | ||||||
|  |       }); | ||||||
|  |  | ||||||
|       // dynamically add accessible routes |       // dynamically add accessible routes | ||||||
|       router.addRoutes(accessRoutes) |       router.addRoutes(accessRoutes); | ||||||
|  |  | ||||||
|       // reset visited views and cached views |       // reset visited views and cached views | ||||||
|       dispatch('tagsView/delAllViews', null, { root: true }) |       dispatch("tagsView/delAllViews", null, { root: true }); | ||||||
|  |  | ||||||
|       resolve() |       resolve(); | ||||||
|     }) |     }); | ||||||
|   } |   }, | ||||||
| } | }; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   namespaced: true, |   namespaced: true, | ||||||
|   state, |   state, | ||||||
|   mutations, |   mutations, | ||||||
|   actions |   actions, | ||||||
| } | }; | ||||||
|  | |||||||
| @ -48,6 +48,8 @@ service.interceptors.response.use( | |||||||
|       return response.data |       return response.data | ||||||
|     } |     } | ||||||
|     const code = response.data.code |     const code = response.data.code | ||||||
|  |     console.log("xx",code) | ||||||
|  |  | ||||||
|     if (code === 401) { |     if (code === 401) { | ||||||
|       store.dispatch('user/resetToken') |       store.dispatch('user/resetToken') | ||||||
|       if (location.href.indexOf('login') !== -1) { |       if (location.href.indexOf('login') !== -1) { | ||||||
|  | |||||||
| @ -15,6 +15,11 @@ | |||||||
|                             clearable size="small" @keyup.enter.native="handleQuery" /> |                             clearable size="small" @keyup.enter.native="handleQuery" /> | ||||||
|                     </el-form-item> --> |                     </el-form-item> --> | ||||||
|  |  | ||||||
|  |                     <el-form-item label="时间范围"> | ||||||
|  |                         <el-date-picker v-model="times" type="daterange" align="right" unlink-panels range-separator="至" | ||||||
|  |                             start-placeholder="开始日期" end-placeholder="结束日期" :picker-options="pickerOptions"> | ||||||
|  |                         </el-date-picker> | ||||||
|  |                     </el-form-item> | ||||||
|                     <el-form-item> |                     <el-form-item> | ||||||
|                         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> |                         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> | ||||||
|                         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> |                         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> | ||||||
| @ -37,6 +42,14 @@ | |||||||
|                             size="mini" :disabled="multiple" @click="handleDelete">删除 |                             size="mini" :disabled="multiple" @click="handleDelete">删除 | ||||||
|                         </el-button> |                         </el-button> | ||||||
|                     </el-col> |                     </el-col> | ||||||
|  |                     <el-col :span="1.5"> | ||||||
|  |                         <el-popconfirm class="delete-popconfirm" title="确定要导出记录吗?" confirm-button-text="确定" | ||||||
|  |                             @confirm="handleExport"> | ||||||
|  |                             <el-button slot="reference" v-permisaction="['admin:mmAlarmLog:export']" | ||||||
|  |                                 icon="el-icon-files" size="mini">导出 | ||||||
|  |                             </el-button> | ||||||
|  |                         </el-popconfirm> | ||||||
|  |                     </el-col> | ||||||
|                     <el-col :span="1.5"> |                     <el-col :span="1.5"> | ||||||
|                         <el-popconfirm class="delete-popconfirm" title="确认要清除所有吗?" confirm-button-text="清除" |                         <el-popconfirm class="delete-popconfirm" title="确认要清除所有吗?" confirm-button-text="清除" | ||||||
|                             @confirm="handleClearAll()"> |                             @confirm="handleClearAll()"> | ||||||
| @ -107,8 +120,9 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import { addMmAlarmLog, delMmAlarmLog, getMmAlarmLog, listMmAlarmLog, updateMmAlarmLog, clearAllAlarmLog } from '@/api/admin/mm-alarm-log' | import { addMmAlarmLog, delMmAlarmLog, getMmAlarmLog, listMmAlarmLog, updateMmAlarmLog, clearAllAlarmLog, exportMmAlarmLog } from '@/api/admin/mm-alarm-log' | ||||||
| import { getMmMachineList } from '@/api/admin/mm-machine' | import { getMmMachineList } from '@/api/admin/mm-machine' | ||||||
|  | import { resolveBlob } from '@/utils/zipdownload' | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     name: 'MmAlarmLog', |     name: 'MmAlarmLog', | ||||||
| @ -137,11 +151,13 @@ export default { | |||||||
|             mmAlarmLogList: [], |             mmAlarmLogList: [], | ||||||
|  |  | ||||||
|             // 关系表类型 |             // 关系表类型 | ||||||
|  |             times: [], | ||||||
|             // 查询参数 |             // 查询参数 | ||||||
|             queryParams: { |             queryParams: { | ||||||
|                 pageIndex: 1, |                 pageIndex: 1, | ||||||
|                 pageSize: 10, |                 pageSize: 10, | ||||||
|  |                 startTime: undefined, | ||||||
|  |                 endTime: undefined, | ||||||
|                 machineId: undefined, |                 machineId: undefined, | ||||||
|                 biosId: undefined, |                 biosId: undefined, | ||||||
|                 idOrder: 'desc', |                 idOrder: 'desc', | ||||||
| @ -153,7 +169,34 @@ export default { | |||||||
|             rules: { |             rules: { | ||||||
|                 machineId: [{ required: true, message: '设备id不能为空', trigger: 'blur' }], |                 machineId: [{ required: true, message: '设备id不能为空', trigger: 'blur' }], | ||||||
|                 biosId: [{ required: true, message: '设备码不能为空', trigger: 'blur' }], |                 biosId: [{ required: true, message: '设备码不能为空', trigger: 'blur' }], | ||||||
|             } |             }, | ||||||
|  |             pickerOptions: { | ||||||
|  |                 shortcuts: [{ | ||||||
|  |                     text: '最近一周', | ||||||
|  |                     onClick(picker) { | ||||||
|  |                         const end = new Date(); | ||||||
|  |                         const start = new Date(); | ||||||
|  |                         start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); | ||||||
|  |                         picker.$emit('pick', [start, end]); | ||||||
|  |                     } | ||||||
|  |                 }, { | ||||||
|  |                     text: '最近一个月', | ||||||
|  |                     onClick(picker) { | ||||||
|  |                         const end = new Date(); | ||||||
|  |                         const start = new Date(); | ||||||
|  |                         start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); | ||||||
|  |                         picker.$emit('pick', [start, end]); | ||||||
|  |                     } | ||||||
|  |                 }, { | ||||||
|  |                     text: '最近三个月', | ||||||
|  |                     onClick(picker) { | ||||||
|  |                         const end = new Date(); | ||||||
|  |                         const start = new Date(); | ||||||
|  |                         start.setTime(start.getTime() - 3600 * 1000 * 24 * 90); | ||||||
|  |                         picker.$emit('pick', [start, end]); | ||||||
|  |                     } | ||||||
|  |                 }] | ||||||
|  |             }, | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     created() { |     created() { | ||||||
| @ -164,7 +207,20 @@ export default { | |||||||
|         /** 查询参数列表 */ |         /** 查询参数列表 */ | ||||||
|         getList() { |         getList() { | ||||||
|             this.loading = true |             this.loading = true | ||||||
|             listMmAlarmLog(this.addDateRange(this.queryParams, this.dateRange)).then(response => { |             this.queryParams.startTime = undefined | ||||||
|  |             this.queryParams.endTime = undefined | ||||||
|  |  | ||||||
|  |             if (this.times) { | ||||||
|  |                 console.log(this.times.length) | ||||||
|  |                 if (this.times.length >= 0) { | ||||||
|  |                     this.queryParams.startTime = this.times[0] | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (this.times.length >= 1) { | ||||||
|  |                     this.queryParams.endTime = this.times[1] | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             listMmAlarmLog(this.queryParams).then(response => { | ||||||
|                 this.mmAlarmLogList = response.data.list |                 this.mmAlarmLogList = response.data.list | ||||||
|                 this.total = response.data.count |                 this.total = response.data.count | ||||||
|                 this.loading = false |                 this.loading = false | ||||||
| @ -300,6 +356,36 @@ export default { | |||||||
|             }).finally(() => { |             }).finally(() => { | ||||||
|                 this.loading = false |                 this.loading = false | ||||||
|             }) |             }) | ||||||
|  |         }, | ||||||
|  |         // 导出按钮 | ||||||
|  |         handleExport() { | ||||||
|  |             this.loading = true | ||||||
|  |             this.queryParams.startTime = undefined | ||||||
|  |             this.queryParams.endTime = undefined | ||||||
|  |  | ||||||
|  |             if (this.times) { | ||||||
|  |                 console.log(this.times.length) | ||||||
|  |                 if (this.times.length >= 0) { | ||||||
|  |                     this.queryParams.startTime = this.times[0] | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (this.times.length >= 1) { | ||||||
|  |                     this.queryParams.endTime = this.times[1] | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             exportMmAlarmLog(this.queryParams) | ||||||
|  |                 .then(response => { | ||||||
|  |                     console.log(response) | ||||||
|  |                     resolveBlob(response, '钱包告警记录.xlsx') | ||||||
|  |                     this.msgSuccess('导出成功') | ||||||
|  |                 }) | ||||||
|  |                 .catch(err => { | ||||||
|  |                     console.log(err) | ||||||
|  |                 }) | ||||||
|  |                 .finally(() => { | ||||||
|  |                     this.loading = false | ||||||
|  |                 }) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										90
									
								
								src/views/login/google_auth.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/views/login/google_auth.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,90 @@ | |||||||
|  | <template> | ||||||
|  |   <el-dialog | ||||||
|  |     title="谷歌验证码验证" | ||||||
|  |     :visible.sync="visible" | ||||||
|  |     width="400px" | ||||||
|  |     :close-on-click-modal="false" | ||||||
|  |     :before-close="handleCancel" | ||||||
|  |     :show-close="false"  | ||||||
|  |     :close-on-press-escape="false" | ||||||
|  |     center | ||||||
|  |     class="google-dialog" | ||||||
|  |   > | ||||||
|  |     <div class="dialog-body"> | ||||||
|  |       <p class="tip-text">为了您的账户安全,请输入谷歌验证码</p> | ||||||
|  |       <el-input | ||||||
|  |         v-model="code" | ||||||
|  |         placeholder="请输入6位谷歌验证码" | ||||||
|  |         maxlength="6" | ||||||
|  |         class="code-input" | ||||||
|  |         ref="codeInput" | ||||||
|  |         @keyup.enter.native="handleConfirm" | ||||||
|  |       ></el-input> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <div slot="footer" class="dialog-footer"> | ||||||
|  |       <el-button @click="handleCancel">取消</el-button> | ||||||
|  |       <el-button | ||||||
|  |         type="primary" | ||||||
|  |         :loading="loading" | ||||||
|  |         :disabled="code.length !== 6" | ||||||
|  |         @click="handleConfirm" | ||||||
|  |       >验证</el-button> | ||||||
|  |     </div> | ||||||
|  |   </el-dialog> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | export default { | ||||||
|  |   name: "GoogleAuth", | ||||||
|  |   props: { | ||||||
|  |     visible: { | ||||||
|  |       type: Boolean, | ||||||
|  |       required: true | ||||||
|  |     }, | ||||||
|  |     loading: { | ||||||
|  |       type: Boolean, | ||||||
|  |       default: false | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       code: "" | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   watch: { | ||||||
|  |     visible(val) { | ||||||
|  |       if (val) { | ||||||
|  |         this.code = ""; | ||||||
|  |         this.$nextTick(() => this.$refs.codeInput.focus()); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     handleConfirm() { | ||||||
|  |       this.$emit("confirm", this.code); | ||||||
|  |     }, | ||||||
|  |     handleCancel() { | ||||||
|  |       this.$emit("cancel"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style scoped> | ||||||
|  | .google-dialog ::v-deep .el-dialog { | ||||||
|  |   border-radius: 10px; | ||||||
|  | } | ||||||
|  | .dialog-body { | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  | .tip-text { | ||||||
|  |   font-size: 14px; | ||||||
|  |   color: #666; | ||||||
|  |   margin-bottom: 15px; | ||||||
|  | } | ||||||
|  | .code-input { | ||||||
|  |   width: 80%; | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @ -30,58 +30,27 @@ | |||||||
|       <div class="login-border"> |       <div class="login-border"> | ||||||
|         <div class="login-main"> |         <div class="login-main"> | ||||||
|           <div class="login-title">用户登录</div> |           <div class="login-title">用户登录</div> | ||||||
|           <el-form |           <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on" | ||||||
|             ref="loginForm" |             label-position="left"> | ||||||
|             :model="loginForm" |  | ||||||
|             :rules="loginRules" |  | ||||||
|             class="login-form" |  | ||||||
|             autocomplete="on" |  | ||||||
|             label-position="left" |  | ||||||
|           > |  | ||||||
|             <el-form-item prop="username"> |             <el-form-item prop="username"> | ||||||
|               <span class="svg-container"> |               <span class="svg-container"> | ||||||
|                 <i class="el-icon-user" /> |                 <i class="el-icon-user" /> | ||||||
|               </span> |               </span> | ||||||
|               <el-input |               <el-input ref="username" v-model="loginForm.username" placeholder="用户名" name="username" type="text" | ||||||
|                 ref="username" |                 tabindex="1" autocomplete="on" /> | ||||||
|                 v-model="loginForm.username" |  | ||||||
|                 placeholder="用户名" |  | ||||||
|                 name="username" |  | ||||||
|                 type="text" |  | ||||||
|                 tabindex="1" |  | ||||||
|                 autocomplete="on" |  | ||||||
|               /> |  | ||||||
|             </el-form-item> |             </el-form-item> | ||||||
|  |  | ||||||
|             <el-tooltip |             <el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual> | ||||||
|               v-model="capsTooltip" |  | ||||||
|               content="Caps lock is On" |  | ||||||
|               placement="right" |  | ||||||
|               manual |  | ||||||
|             > |  | ||||||
|               <el-form-item prop="password"> |               <el-form-item prop="password"> | ||||||
|                 <span class="svg-container"> |                 <span class="svg-container"> | ||||||
|                   <svg-icon icon-class="password" /> |                   <svg-icon icon-class="password" /> | ||||||
|                 </span> |                 </span> | ||||||
|                 <el-input |                 <el-input :key="passwordType" ref="password" v-model="loginForm.password" :type="passwordType" | ||||||
|                   :key="passwordType" |                   placeholder="密码" name="password" tabindex="2" autocomplete="on" @keyup.native="checkCapslock" | ||||||
|                   ref="password" |                   @blur="capsTooltip = false" @keyup.enter.native="handleLogin" /> | ||||||
|                   v-model="loginForm.password" |  | ||||||
|                   :type="passwordType" |  | ||||||
|                   placeholder="密码" |  | ||||||
|                   name="password" |  | ||||||
|                   tabindex="2" |  | ||||||
|                   autocomplete="on" |  | ||||||
|                   @keyup.native="checkCapslock" |  | ||||||
|                   @blur="capsTooltip = false" |  | ||||||
|                   @keyup.enter.native="handleLogin" |  | ||||||
|                 /> |  | ||||||
|                 <span class="show-pwd" @click="showPwd"> |                 <span class="show-pwd" @click="showPwd"> | ||||||
|                   <svg-icon |                   <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open' | ||||||
|                     :icon-class=" |                     " /> | ||||||
|                       passwordType === 'password' ? 'eye' : 'eye-open' |  | ||||||
|                     " |  | ||||||
|                   /> |  | ||||||
|                 </span> |                 </span> | ||||||
|               </el-form-item> |               </el-form-item> | ||||||
|             </el-tooltip> |             </el-tooltip> | ||||||
| @ -89,47 +58,26 @@ | |||||||
|               <span class="svg-container"> |               <span class="svg-container"> | ||||||
|                 <svg-icon icon-class="validCode" /> |                 <svg-icon icon-class="validCode" /> | ||||||
|               </span> |               </span> | ||||||
|               <el-input |               <el-input ref="username" v-model="loginForm.code" placeholder="验证码" name="username" type="text" | ||||||
|                 ref="username" |                 tabindex="3" maxlength="5" autocomplete="off" style="width: 75%" @keyup.enter.native="handleLogin" /> | ||||||
|                 v-model="loginForm.code" |  | ||||||
|                 placeholder="验证码" |  | ||||||
|                 name="username" |  | ||||||
|                 type="text" |  | ||||||
|                 tabindex="3" |  | ||||||
|                 maxlength="5" |  | ||||||
|                 autocomplete="off" |  | ||||||
|                 style="width: 75%" |  | ||||||
|                 @keyup.enter.native="handleLogin" |  | ||||||
|               /> |  | ||||||
|             </el-form-item> |             </el-form-item> | ||||||
|             <div |             <div class="login-code" style=" | ||||||
|               class="login-code" |  | ||||||
|               style=" |  | ||||||
|                 cursor: pointer; |                 cursor: pointer; | ||||||
|                 width: 30%; |                 width: 30%; | ||||||
|                 height: 48px; |                 height: 48px; | ||||||
|                 float: right; |                 float: right; | ||||||
|                 background-color: #f0f1f5; |                 background-color: #f0f1f5; | ||||||
|               " |               "> | ||||||
|             > |               <img style=" | ||||||
|               <img |  | ||||||
|                 style=" |  | ||||||
|                   height: 48px; |                   height: 48px; | ||||||
|                   width: 100%; |                   width: 100%; | ||||||
|                   border: 1px solid rgba(0, 0, 0, 0.1); |                   border: 1px solid rgba(0, 0, 0, 0.1); | ||||||
|                   border-radius: 5px; |                   border-radius: 5px; | ||||||
|                 " |                 " :src="codeUrl" @click="getCode"> | ||||||
|                 :src="codeUrl" |  | ||||||
|                 @click="getCode" |  | ||||||
|               > |  | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
|             <el-button |             <el-button :loading="loading" type="primary" style="width: 100%; padding: 12px 20px; margin-bottom: 30px" | ||||||
|               :loading="loading" |               @click.native.prevent="handleLogin"> | ||||||
|               type="primary" |  | ||||||
|               style="width: 100%; padding: 12px 20px; margin-bottom: 30px" |  | ||||||
|               @click.native.prevent="handleLogin" |  | ||||||
|             > |  | ||||||
|               <span v-if="!loading">登 录</span> |               <span v-if="!loading">登 录</span> | ||||||
|               <span v-else>登 录 中...</span> |               <span v-else>登 录 中...</span> | ||||||
|             </el-button> |             </el-button> | ||||||
| @ -146,11 +94,7 @@ | |||||||
|       <br> |       <br> | ||||||
|       <social-sign /> |       <social-sign /> | ||||||
|     </el-dialog> |     </el-dialog> | ||||||
|     <div |     <div id="bottom_layer" class="s-bottom-layer s-isindex-wrap" style="visibility: visible; width: 100%"> | ||||||
|       id="bottom_layer" |  | ||||||
|       class="s-bottom-layer s-isindex-wrap" |  | ||||||
|       style="visibility: visible; width: 100%" |  | ||||||
|     > |  | ||||||
|       <div class="s-bottom-layer-content"> |       <div class="s-bottom-layer-content"> | ||||||
|  |  | ||||||
|         <div class="lh"> |         <div class="lh"> | ||||||
| @ -163,11 +107,7 @@ | |||||||
|             <div class="rest_info_tip"> |             <div class="rest_info_tip"> | ||||||
|               <div class="tip-wrapper"> |               <div class="tip-wrapper"> | ||||||
|                 <div class="lh tip-item" style="display: none"> |                 <div class="lh tip-item" style="display: none"> | ||||||
|                   <a |                   <a class="text-color" href="https://beian.miit.gov.cn" target="_blank"> | ||||||
|                     class="text-color" |  | ||||||
|                     href="https://beian.miit.gov.cn" |  | ||||||
|                     target="_blank" |  | ||||||
|                   > |  | ||||||
|                     沪ICP备XXXXXXXXX号-1 |                     沪ICP备XXXXXXXXX号-1 | ||||||
|                   </a> |                   </a> | ||||||
|                 </div> |                 </div> | ||||||
| @ -177,17 +117,26 @@ | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  |     <GoogleAuth :visible.sync="showGoogleVerifyDialog" :loading="googleVerifying" @confirm="submitGoogleCode" | ||||||
|  |       @cancel="showGoogleVerifyDialog = false" /> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import { getCodeImg } from '@/api/login' | import { getCodeImg } from '@/api/login' | ||||||
|  | import { needCheckGoogleAuth, checkGoogleCode } from '@/api/admin/sys-user' | ||||||
| import moment from 'moment' | import moment from 'moment' | ||||||
| import SocialSign from './components/SocialSignin' | import SocialSign from './components/SocialSignin' | ||||||
|  | import GoogleAuth from './google_auth' | ||||||
|  | import { setToken } from "@/utils/auth"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   name: 'Login', |   name: 'Login', | ||||||
|   components: { SocialSign }, |   components: { | ||||||
|  |     SocialSign, | ||||||
|  |     GoogleAuth | ||||||
|  |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|       codeUrl: '', |       codeUrl: '', | ||||||
| @ -218,12 +167,15 @@ export default { | |||||||
|       redirect: undefined, |       redirect: undefined, | ||||||
|       otherQuery: {}, |       otherQuery: {}, | ||||||
|       currentTime: null, |       currentTime: null, | ||||||
|       sysInfo: '' |       sysInfo: '', | ||||||
|  |       showGoogleVerifyDialog: false, | ||||||
|  |       googleVerifying: false, | ||||||
|  |       googleTokenCache: '' | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   watch: { |   watch: { | ||||||
|     $route: { |     $route: { | ||||||
|       handler: function(route) { |       handler: function (route) { | ||||||
|         const query = route.query |         const query = route.query | ||||||
|         if (query) { |         if (query) { | ||||||
|           this.redirect = query.redirect |           this.redirect = query.redirect | ||||||
| @ -252,10 +204,11 @@ export default { | |||||||
|   }, |   }, | ||||||
|   destroyed() { |   destroyed() { | ||||||
|     clearInterval(this.timer) |     clearInterval(this.timer) | ||||||
|     window.removeEventListener('resize', () => {}) |     window.removeEventListener('resize', () => { }) | ||||||
|     // window.removeEventListener('storage', this.afterQRScan) |     // window.removeEventListener('storage', this.afterQRScan) | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|  |     setToken, | ||||||
|     getSystemSetting() { |     getSystemSetting() { | ||||||
|       this.$store.dispatch('system/settingDetail').then((ret) => { |       this.$store.dispatch('system/settingDetail').then((ret) => { | ||||||
|         this.sysInfo = ret |         this.sysInfo = ret | ||||||
| @ -300,26 +253,55 @@ export default { | |||||||
|         this.$refs.password.focus() |         this.$refs.password.focus() | ||||||
|       }) |       }) | ||||||
|     }, |     }, | ||||||
|     handleLogin() { |     async validateForm() { | ||||||
|       this.$refs.loginForm.validate((valid) => { |       return new Promise((resolve) => { | ||||||
|         if (valid) { |         this.$refs.loginForm.validate((valid) => { | ||||||
|           this.loading = true |           resolve(valid); | ||||||
|           this.$store |         }); | ||||||
|             .dispatch('user/login', this.loginForm) |       }); | ||||||
|             .then(() => { |     }, | ||||||
|               this.$router |     async handleLogin() { | ||||||
|                 .push({ path: this.redirect || '/', query: this.otherQuery }) |       const valid = await this.validateForm(); | ||||||
|                 .catch(() => {}) |       if (!valid) return; | ||||||
|             }) |  | ||||||
|             .catch(() => { |       this.loading = true; | ||||||
|               this.loading = false |  | ||||||
|               this.getCode() |       try { | ||||||
|             }) |         const res = await this.$store.dispatch('user/login', { | ||||||
|         } else { |           userInfo: this.loginForm, | ||||||
|           console.log('error submit!!') |           handleResponse: async (res) => { | ||||||
|           return false |  | ||||||
|  |             if (res.code === 200) { | ||||||
|  |               let headers = { | ||||||
|  |                 "Authorization": "Bearer " + res.token | ||||||
|  |               } | ||||||
|  |               const res2 = await needCheckGoogleAuth(headers); | ||||||
|  |               let cacheToken = false; | ||||||
|  |  | ||||||
|  |               if (res2 && res2.code === 200 && res2.data) { | ||||||
|  |                 this.googleTokenCache = res.token; | ||||||
|  |                 this.showGoogleVerifyDialog = true; | ||||||
|  |                 cacheToken = false; | ||||||
|  |               } else { | ||||||
|  |                 cacheToken = true; | ||||||
|  |               } | ||||||
|  |  | ||||||
|  |               return cacheToken; | ||||||
|  |             } else { | ||||||
|  |               this.$message.error(res.message || '登录失败'); | ||||||
|  |               return false; | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         if (res) { | ||||||
|  |           this.$router.push({ path: this.redirect || '/', query: this.otherQuery }).catch(() => { }); | ||||||
|         } |         } | ||||||
|       }) |       } catch (error) { | ||||||
|  |         this.getCode(); | ||||||
|  |       } finally { | ||||||
|  |         this.loading = false; | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     getOtherQuery(query) { |     getOtherQuery(query) { | ||||||
|       return Object.keys(query).reduce((acc, cur) => { |       return Object.keys(query).reduce((acc, cur) => { | ||||||
| @ -328,6 +310,30 @@ export default { | |||||||
|         } |         } | ||||||
|         return acc |         return acc | ||||||
|       }, {}) |       }, {}) | ||||||
|  |     }, | ||||||
|  |     submitGoogleCode(code) { | ||||||
|  |       this.googleVerifying = true | ||||||
|  |       let headers = { | ||||||
|  |         "Authorization": "Bearer " + this.googleTokenCache | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       checkGoogleCode({ code }, headers) | ||||||
|  |         .then((response) => { | ||||||
|  |           if (response && response.code === 200) { | ||||||
|  |             const token = this.googleTokenCache | ||||||
|  |             this.$store.commit("user/SET_TOKEN", token) | ||||||
|  |             setToken(token) | ||||||
|  |  | ||||||
|  |             this.$message.success("登录成功") | ||||||
|  |             this.$router.push({ path: this.redirect || '/', query: this.otherQuery }) | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |         .catch((err) => { | ||||||
|  |           console.log("err", err) | ||||||
|  |         }) | ||||||
|  |         .finally(() => { | ||||||
|  |           this.googleVerifying = false | ||||||
|  |         }) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @ -355,76 +361,95 @@ $cursor: #fff; | |||||||
|   line-height: 39px; |   line-height: 39px; | ||||||
|   // background: #0e6cff; |   // background: #0e6cff; | ||||||
| } | } | ||||||
|  |  | ||||||
| #bottom_layer .lh { | #bottom_layer .lh { | ||||||
|   display: inline-block; |   display: inline-block; | ||||||
|   margin-right: 14px; |   margin-right: 14px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #bottom_layer .lh .emphasize { | #bottom_layer .lh .emphasize { | ||||||
|   text-decoration: underline; |   text-decoration: underline; | ||||||
|   font-weight: 700; |   font-weight: 700; | ||||||
| } | } | ||||||
|  |  | ||||||
| #bottom_layer .lh:last-child { | #bottom_layer .lh:last-child { | ||||||
|   margin-left: -2px; |   margin-left: -2px; | ||||||
|   margin-right: 0; |   margin-right: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| #bottom_layer .lh.activity { | #bottom_layer .lh.activity { | ||||||
|   font-weight: 700; |   font-weight: 700; | ||||||
|   text-decoration: underline; |   text-decoration: underline; | ||||||
| } | } | ||||||
|  |  | ||||||
| #bottom_layer a { | #bottom_layer a { | ||||||
|   font-size: 12px; |   font-size: 12px; | ||||||
|   text-decoration: none; |   text-decoration: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| #bottom_layer .text-color { | #bottom_layer .text-color { | ||||||
|   color: #bbb; |   color: #bbb; | ||||||
| } | } | ||||||
|  |  | ||||||
| #bottom_layer .aria-img { | #bottom_layer .aria-img { | ||||||
|   width: 49px; |   width: 49px; | ||||||
|   height: 20px; |   height: 20px; | ||||||
|   margin-bottom: -5px; |   margin-bottom: -5px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #bottom_layer a:hover { | #bottom_layer a:hover { | ||||||
|   color: #fff; |   color: #fff; | ||||||
| } | } | ||||||
|  |  | ||||||
| #bottom_layer .s-bottom-layer-content { | #bottom_layer .s-bottom-layer-content { | ||||||
|   margin: 0 17px; |   margin: 0 17px; | ||||||
|   text-align: center; |   text-align: center; | ||||||
| } | } | ||||||
|  |  | ||||||
| #bottom_layer .s-bottom-layer-content .auto-transform-line { | #bottom_layer .s-bottom-layer-content .auto-transform-line { | ||||||
|   display: inline; |   display: inline; | ||||||
| } | } | ||||||
|  |  | ||||||
| #bottom_layer .s-bottom-layer-content .auto-transform-line:first-child { | #bottom_layer .s-bottom-layer-content .auto-transform-line:first-child { | ||||||
|   margin-right: 14px; |   margin-right: 14px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .s-bottom-space { | .s-bottom-space { | ||||||
|   position: static; |   position: static; | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   height: 40px; |   height: 40px; | ||||||
|   margin: 23px auto 12px; |   margin: 23px auto 12px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #bottom_layer .open-content-info a:hover { | #bottom_layer .open-content-info a:hover { | ||||||
|   color: #fff; |   color: #fff; | ||||||
| } | } | ||||||
|  |  | ||||||
| #bottom_layer .open-content-info .text-color { | #bottom_layer .open-content-info .text-color { | ||||||
|   color: #626675; |   color: #626675; | ||||||
| } | } | ||||||
|  |  | ||||||
| .open-content-info { | .open-content-info { | ||||||
|   position: relative; |   position: relative; | ||||||
|   display: inline-block; |   display: inline-block; | ||||||
|   width: 20px; |   width: 20px; | ||||||
| } | } | ||||||
| .open-content-info > span { |  | ||||||
|  | .open-content-info>span { | ||||||
|   cursor: pointer; |   cursor: pointer; | ||||||
|   font-size: 14px; |   font-size: 14px; | ||||||
| } | } | ||||||
| .open-content-info > span:hover { |  | ||||||
|  | .open-content-info>span:hover { | ||||||
|   color: #fff; |   color: #fff; | ||||||
| } | } | ||||||
|  |  | ||||||
| .open-content-info .tip-hover-panel { | .open-content-info .tip-hover-panel { | ||||||
|   position: absolute; |   position: absolute; | ||||||
|   display: none; |   display: none; | ||||||
|   padding-bottom: 18px; |   padding-bottom: 18px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .open-content-info .tip-hover-panel .rest_info_tip { | .open-content-info .tip-hover-panel .rest_info_tip { | ||||||
|   max-width: 560px; |   max-width: 560px; | ||||||
|   padding: 8px 12px 8px 12px; |   padding: 8px 12px 8px 12px; | ||||||
| @ -434,29 +459,31 @@ $cursor: #fff; | |||||||
|   box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1); |   box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1); | ||||||
|   text-align: left; |   text-align: left; | ||||||
| } | } | ||||||
|  |  | ||||||
| .open-content-info .tip-hover-panel .rest_info_tip .tip-wrapper { | .open-content-info .tip-hover-panel .rest_info_tip .tip-wrapper { | ||||||
|   white-space: nowrap; |   white-space: nowrap; | ||||||
|   line-height: 20px; |   line-height: 20px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .open-content-info .tip-hover-panel .rest_info_tip .tip-wrapper .tip-item { | .open-content-info .tip-hover-panel .rest_info_tip .tip-wrapper .tip-item { | ||||||
|   height: 20px; |   height: 20px; | ||||||
|   line-height: 20px; |   line-height: 20px; | ||||||
| } | } | ||||||
| .open-content-info |  | ||||||
|   .tip-hover-panel | .open-content-info .tip-hover-panel .rest_info_tip .tip-wrapper .tip-item:last-child { | ||||||
|   .rest_info_tip |  | ||||||
|   .tip-wrapper |  | ||||||
|   .tip-item:last-child { |  | ||||||
|   margin-right: 0; |   margin-right: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| @media screen and (max-width: 515px) { | @media screen and (max-width: 515px) { | ||||||
|   .open-content-info { |   .open-content-info { | ||||||
|     width: 16px; |     width: 16px; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .open-content-info .tip-hover-panel { |   .open-content-info .tip-hover-panel { | ||||||
|     right: -16px !important; |     right: -16px !important; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| .footer { | .footer { | ||||||
|   background-color: #0e6cff; |   background-color: #0e6cff; | ||||||
|   margin-bottom: -20px; |   margin-bottom: -20px; | ||||||
| @ -517,6 +544,7 @@ $cursor: #fff; | |||||||
|   display: -webkit-box; |   display: -webkit-box; | ||||||
|   display: -ms-flexbox; |   display: -ms-flexbox; | ||||||
|   display: flex; |   display: flex; | ||||||
|  |  | ||||||
|   .login-time { |   .login-time { | ||||||
|     position: absolute; |     position: absolute; | ||||||
|     left: 25px; |     left: 25px; | ||||||
| @ -613,6 +641,7 @@ $cursor: #fff; | |||||||
|     color: #454545; |     color: #454545; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| $bg: #2d3a4b; | $bg: #2d3a4b; | ||||||
| $dark_gray: #889aa4; | $dark_gray: #889aa4; | ||||||
| $light_gray: #eee; | $light_gray: #eee; | ||||||
| @ -670,18 +699,22 @@ $light_gray: #eee; | |||||||
|     .thirdparty-button { |     .thirdparty-button { | ||||||
|       display: none; |       display: none; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     .login-weaper { |     .login-weaper { | ||||||
|       width: 100%; |       width: 100%; | ||||||
|       padding: 0 30px; |       padding: 0 30px; | ||||||
|       box-sizing: border-box; |       box-sizing: border-box; | ||||||
|       box-shadow: none; |       box-shadow: none; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     .login-main { |     .login-main { | ||||||
|       width: 80%; |       width: 80%; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     .login-left { |     .login-left { | ||||||
|       display: none !important; |       display: none !important; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     .login-border { |     .login-border { | ||||||
|       width: 100%; |       width: 100%; | ||||||
|     } |     } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user