1、对接谷歌验证器
This commit is contained in:
@ -64,6 +64,7 @@
|
||||
"normalize.css": "8.0.1",
|
||||
"nprogress": "0.2.0",
|
||||
"path-to-regexp": "6.1.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"remixicon": "^2.5.0",
|
||||
"sass-resources-loader": "^2.0.3",
|
||||
"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) {
|
||||
return request({
|
||||
url: '/api/v1/sys-user',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
url: "/api/v1/sys-user",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
// 查询用户详细
|
||||
export function getUser(userId) {
|
||||
return request({
|
||||
url: '/api/v1/sys-user/' + userId,
|
||||
method: 'get'
|
||||
})
|
||||
url: "/api/v1/sys-user/" + userId,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserInit() {
|
||||
return request({
|
||||
url: '/api/v1/sys-user/',
|
||||
method: 'get'
|
||||
})
|
||||
url: "/api/v1/sys-user/",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
// 新增用户
|
||||
export function addUser(data) {
|
||||
return request({
|
||||
url: '/api/v1/sys-user',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
url: "/api/v1/sys-user",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
// 修改用户
|
||||
export function updateUser(data) {
|
||||
return request({
|
||||
url: '/api/v1/sys-user',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
url: "/api/v1/sys-user",
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
export function delUser(data) {
|
||||
return request({
|
||||
url: '/api/v1/sys-user',
|
||||
method: 'delete',
|
||||
data: data
|
||||
})
|
||||
url: "/api/v1/sys-user",
|
||||
method: "delete",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
// 导出用户
|
||||
export function exportUser(query) {
|
||||
return request({
|
||||
url: '/api/v1/sys-user/export',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
url: "/api/v1/sys-user/export",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
// 用户密码重置
|
||||
export function resetUserPwd(userId, password) {
|
||||
const data = {
|
||||
userId,
|
||||
password
|
||||
}
|
||||
password,
|
||||
};
|
||||
return request({
|
||||
url: '/api/v1/user/pwd/reset',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
url: "/api/v1/user/pwd/reset",
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
// 用户状态修改
|
||||
export function changeUserStatus(e) {
|
||||
const data = {
|
||||
userId: e.userId,
|
||||
status: e.status
|
||||
}
|
||||
status: e.status,
|
||||
};
|
||||
return request({
|
||||
url: '/api/v1/user/status',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
url: "/api/v1/user/status",
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
// 修改用户个人信息
|
||||
export function updateUserProfile(data) {
|
||||
return request({
|
||||
url: '/api/v1/sys-user/profile',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
url: "/api/v1/sys-user/profile",
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
// 下载用户导入模板
|
||||
export function importTemplate() {
|
||||
return request({
|
||||
url: '/api/v1/sys-user/importTemplate',
|
||||
method: 'get'
|
||||
})
|
||||
url: "/api/v1/sys-user/importTemplate",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
// 用户密码重置
|
||||
export function updateUserPwd(oldPassword, newPassword) {
|
||||
const data = {
|
||||
oldPassword,
|
||||
newPassword
|
||||
}
|
||||
newPassword,
|
||||
};
|
||||
return request({
|
||||
url: '/api/v1/user/pwd/set',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
url: "/api/v1/user/pwd/set",
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
// 用户头像上传
|
||||
export function uploadAvatar(data) {
|
||||
return request({
|
||||
url: '/api/v1/user/avatar',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
url: "/api/v1/user/avatar",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
// 查询用户个人信息
|
||||
export function getUserProfile() {
|
||||
return request({
|
||||
url: '/api/v1/user/profile',
|
||||
method: 'get'
|
||||
})
|
||||
url: "/api/v1/user/profile",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
// 查询用户列表
|
||||
export function getUserOptions() {
|
||||
return request({
|
||||
url: '/api/v1/sys-user/options',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
url: "/api/v1/sys-user/options",
|
||||
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 />
|
||||
</right-panel>
|
||||
</div>
|
||||
|
||||
<!-- 绑定谷歌验证码弹窗 -->
|
||||
<set-google-secret :visible.sync="showSetGooleSecret" :otpAuthUrl="needGoolAuthData.otpAuthUrl"
|
||||
:secret="needGoolAuthData.secret" @success="handleBindSuccess"></set-google-secret>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RightPanel from '@/components/RightPanel'
|
||||
import { needGoolAuth, setGoogleAuth } from '@/api/admin/sys-user'
|
||||
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
|
||||
import SetGoogleSecret from './SetGoogleSecret'
|
||||
import ResizeMixin from './mixin/ResizeHandler'
|
||||
import { mapState } from 'vuex'
|
||||
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'
|
||||
|
||||
export default {
|
||||
@ -33,11 +39,14 @@ export default {
|
||||
RightPanel,
|
||||
Settings,
|
||||
Sidebar,
|
||||
TagsView
|
||||
TagsView,
|
||||
SetGoogleSecret
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
voice: null,
|
||||
showSetGooleSecret: false,
|
||||
needGoolAuthData: {}
|
||||
}
|
||||
},
|
||||
mixins: [ResizeMixin],
|
||||
@ -63,35 +72,52 @@ export default {
|
||||
},
|
||||
|
||||
created() {
|
||||
// if (!this.roles.includes('admin')) {
|
||||
// this.currentRole = 'editorDashboard'
|
||||
// }
|
||||
this.getNeedGoolAuth()
|
||||
|
||||
if (checkPermisAction(['admin:mmAlarmLog:notice'])) {
|
||||
this.$confirm('是否接收警告?', '提示', {
|
||||
distinguishCancelAndClose: true,
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.voice = new Audio(dingSound)
|
||||
// this.$confirm('是否接收警告?', '提示', {
|
||||
// distinguishCancelAndClose: true,
|
||||
// confirmButtonText: '确定',
|
||||
// cancelButtonText: '取消',
|
||||
// type: 'warning'
|
||||
// }).then(() => {
|
||||
// this.voice = new Audio(dingSound)
|
||||
|
||||
this.initWebSocket()
|
||||
}).catch(() => {
|
||||
console.log('取消')
|
||||
});
|
||||
this.initWebSocket()
|
||||
// }).catch(() => {
|
||||
// console.log('取消')
|
||||
// });
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
console.log('断开websocket连接')
|
||||
this.websock.close() // 离开路由之后断开websocket连接
|
||||
// unWsLogout(this.id, this.group).then(response => {
|
||||
// console.log(response.data)
|
||||
// }
|
||||
// )
|
||||
},
|
||||
methods: {
|
||||
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
|
||||
const wsuri = `ws://${process.env.VUE_APP_WEBSOCKET_URL}/ws?token=${this.$store.state.user.token}`
|
||||
|
||||
@ -110,7 +136,6 @@ export default {
|
||||
this.initWebSocket()
|
||||
},
|
||||
websocketonmessage(e) { // 数据接收
|
||||
console.log("ws:", e.data)
|
||||
try {
|
||||
let data = JSON.parse(e.data)
|
||||
|
||||
@ -122,7 +147,7 @@ export default {
|
||||
duration: 0
|
||||
});
|
||||
|
||||
this.playVoice("钱包告警")
|
||||
// this.playVoice("钱包告警")
|
||||
} catch (err) {
|
||||
console.log("接收websocket数据失败:", err)
|
||||
}
|
||||
@ -146,6 +171,9 @@ export default {
|
||||
},
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
||||
},
|
||||
handleBindSuccess() {
|
||||
this.showSetGooleSecret = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,153 +1,174 @@
|
||||
import { login, logout, getInfo, refreshtoken } from '@/api/user'
|
||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
import router, { resetRouter } from '@/router'
|
||||
import storage from '@/utils/storage'
|
||||
import { login, logout, getInfo, refreshtoken } from "@/api/user";
|
||||
import { getToken, setToken, removeToken } from "@/utils/auth";
|
||||
import router, { resetRouter } from "@/router";
|
||||
import storage from "@/utils/storage";
|
||||
|
||||
const state = {
|
||||
token: getToken(),
|
||||
name: '',
|
||||
avatar: '',
|
||||
introduction: '',
|
||||
name: "",
|
||||
avatar: "",
|
||||
introduction: "",
|
||||
roles: [],
|
||||
permissions: [],
|
||||
permisaction: []
|
||||
}
|
||||
permisaction: [],
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
SET_TOKEN: (state, token) => {
|
||||
state.token = token
|
||||
state.token = token;
|
||||
},
|
||||
SET_INTRODUCTION: (state, introduction) => {
|
||||
state.introduction = introduction
|
||||
state.introduction = introduction;
|
||||
},
|
||||
SET_NAME: (state, name) => {
|
||||
state.name = name
|
||||
state.name = name;
|
||||
},
|
||||
SET_AVATAR: (state, avatar) => {
|
||||
if (avatar.indexOf('http') !== -1) {
|
||||
state.avatar = avatar
|
||||
if (avatar.indexOf("http") !== -1) {
|
||||
state.avatar = avatar;
|
||||
} else {
|
||||
state.avatar = process.env.VUE_APP_BASE_API + avatar
|
||||
state.avatar = process.env.VUE_APP_BASE_API + avatar;
|
||||
}
|
||||
},
|
||||
SET_ROLES: (state, roles) => {
|
||||
state.roles = roles
|
||||
state.roles = roles;
|
||||
},
|
||||
SET_PERMISSIONS: (state, permisaction) => {
|
||||
state.permisaction = permisaction
|
||||
}
|
||||
}
|
||||
state.permisaction = permisaction;
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
// user login
|
||||
login({ commit }, userInfo) {
|
||||
login({ commit }, { userInfo, handleResponse }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
login(userInfo).then(response => {
|
||||
const { token } = response
|
||||
commit('SET_TOKEN', token)
|
||||
setToken(token)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
login(userInfo)
|
||||
.then(async(res) => {
|
||||
// 自定义回调处理(判断是否需要谷歌验证)
|
||||
if (handleResponse) {
|
||||
const shouldSetToken =await handleResponse(res);
|
||||
|
||||
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
|
||||
getInfo({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getInfo().then(response => {
|
||||
if (!response || !response.data) {
|
||||
commit('SET_TOKEN', '')
|
||||
removeToken()
|
||||
resolve()
|
||||
}
|
||||
getInfo()
|
||||
.then((response) => {
|
||||
if (!response || !response.data) {
|
||||
commit("SET_TOKEN", "");
|
||||
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
|
||||
if (!roles || roles.length <= 0) {
|
||||
reject('getInfo: roles must be a non-null array!')
|
||||
}
|
||||
commit('SET_PERMISSIONS', permissions)
|
||||
commit('SET_ROLES', roles)
|
||||
commit('SET_NAME', name)
|
||||
commit('SET_AVATAR', avatar)
|
||||
commit('SET_INTRODUCTION', introduction)
|
||||
resolve(response)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
// roles must be a non-empty array
|
||||
if (!roles || roles.length <= 0) {
|
||||
reject("getInfo: roles must be a non-null array!");
|
||||
}
|
||||
commit("SET_PERMISSIONS", permissions);
|
||||
commit("SET_ROLES", roles);
|
||||
commit("SET_NAME", name);
|
||||
commit("SET_AVATAR", avatar);
|
||||
commit("SET_INTRODUCTION", introduction);
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
// 退出系统
|
||||
LogOut({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logout(state.token).then(() => {
|
||||
commit('SET_TOKEN', '')
|
||||
commit('SET_ROLES', [])
|
||||
commit('SET_PERMISSIONS', [])
|
||||
removeToken()
|
||||
storage.clear()
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
logout(state.token)
|
||||
.then(() => {
|
||||
commit("SET_TOKEN", "");
|
||||
commit("SET_ROLES", []);
|
||||
commit("SET_PERMISSIONS", []);
|
||||
removeToken();
|
||||
storage.clear();
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
// 刷新token
|
||||
refreshToken({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
refreshtoken({ token: state.token }).then(response => {
|
||||
const { token } = response
|
||||
commit('SET_TOKEN', token)
|
||||
setToken(token)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
refreshtoken({ token: state.token })
|
||||
.then((response) => {
|
||||
const { token } = response;
|
||||
commit("SET_TOKEN", token);
|
||||
setToken(token);
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// remove token
|
||||
resetToken({ commit }) {
|
||||
return new Promise(resolve => {
|
||||
commit('SET_TOKEN', '')
|
||||
removeToken()
|
||||
resolve()
|
||||
})
|
||||
return new Promise((resolve) => {
|
||||
commit("SET_TOKEN", "");
|
||||
removeToken();
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
|
||||
// dynamically modify permissions
|
||||
changeRoles({ commit, dispatch }, role) {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async resolve => {
|
||||
const token = role + '-token'
|
||||
return new Promise(async (resolve) => {
|
||||
const token = role + "-token";
|
||||
|
||||
commit('SET_TOKEN', token)
|
||||
setToken(token)
|
||||
commit("SET_TOKEN", token);
|
||||
setToken(token);
|
||||
|
||||
const { roles } = await dispatch('getInfo')
|
||||
const { roles } = await dispatch("getInfo");
|
||||
|
||||
resetRouter()
|
||||
resetRouter();
|
||||
|
||||
// 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
|
||||
router.addRoutes(accessRoutes)
|
||||
router.addRoutes(accessRoutes);
|
||||
|
||||
// reset visited views and cached views
|
||||
dispatch('tagsView/delAllViews', null, { root: true })
|
||||
dispatch("tagsView/delAllViews", null, { root: true });
|
||||
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
mutations,
|
||||
actions
|
||||
}
|
||||
actions,
|
||||
};
|
||||
|
||||
@ -48,6 +48,8 @@ service.interceptors.response.use(
|
||||
return response.data
|
||||
}
|
||||
const code = response.data.code
|
||||
console.log("xx",code)
|
||||
|
||||
if (code === 401) {
|
||||
store.dispatch('user/resetToken')
|
||||
if (location.href.indexOf('login') !== -1) {
|
||||
|
||||
@ -15,6 +15,11 @@
|
||||
clearable size="small" @keyup.enter.native="handleQuery" />
|
||||
</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-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>
|
||||
@ -37,6 +42,14 @@
|
||||
size="mini" :disabled="multiple" @click="handleDelete">删除
|
||||
</el-button>
|
||||
</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-popconfirm class="delete-popconfirm" title="确认要清除所有吗?" confirm-button-text="清除"
|
||||
@confirm="handleClearAll()">
|
||||
@ -107,8 +120,9 @@
|
||||
</template>
|
||||
|
||||
<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 { resolveBlob } from '@/utils/zipdownload'
|
||||
|
||||
export default {
|
||||
name: 'MmAlarmLog',
|
||||
@ -137,11 +151,13 @@ export default {
|
||||
mmAlarmLogList: [],
|
||||
|
||||
// 关系表类型
|
||||
|
||||
times: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
machineId: undefined,
|
||||
biosId: undefined,
|
||||
idOrder: 'desc',
|
||||
@ -153,7 +169,34 @@ export default {
|
||||
rules: {
|
||||
machineId: [{ required: true, message: '设备id不能为空', 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() {
|
||||
@ -164,7 +207,20 @@ export default {
|
||||
/** 查询参数列表 */
|
||||
getList() {
|
||||
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.total = response.data.count
|
||||
this.loading = false
|
||||
@ -300,6 +356,36 @@ export default {
|
||||
}).finally(() => {
|
||||
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-main">
|
||||
<div class="login-title">用户登录</div>
|
||||
<el-form
|
||||
ref="loginForm"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
class="login-form"
|
||||
autocomplete="on"
|
||||
label-position="left"
|
||||
>
|
||||
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on"
|
||||
label-position="left">
|
||||
<el-form-item prop="username">
|
||||
<span class="svg-container">
|
||||
<i class="el-icon-user" />
|
||||
</span>
|
||||
<el-input
|
||||
ref="username"
|
||||
v-model="loginForm.username"
|
||||
placeholder="用户名"
|
||||
name="username"
|
||||
type="text"
|
||||
tabindex="1"
|
||||
autocomplete="on"
|
||||
/>
|
||||
<el-input ref="username" v-model="loginForm.username" placeholder="用户名" name="username" type="text"
|
||||
tabindex="1" autocomplete="on" />
|
||||
</el-form-item>
|
||||
|
||||
<el-tooltip
|
||||
v-model="capsTooltip"
|
||||
content="Caps lock is On"
|
||||
placement="right"
|
||||
manual
|
||||
>
|
||||
<el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
|
||||
<el-form-item prop="password">
|
||||
<span class="svg-container">
|
||||
<svg-icon icon-class="password" />
|
||||
</span>
|
||||
<el-input
|
||||
:key="passwordType"
|
||||
ref="password"
|
||||
v-model="loginForm.password"
|
||||
:type="passwordType"
|
||||
placeholder="密码"
|
||||
name="password"
|
||||
tabindex="2"
|
||||
autocomplete="on"
|
||||
@keyup.native="checkCapslock"
|
||||
@blur="capsTooltip = false"
|
||||
@keyup.enter.native="handleLogin"
|
||||
/>
|
||||
<el-input :key="passwordType" ref="password" 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">
|
||||
<svg-icon
|
||||
:icon-class="
|
||||
passwordType === 'password' ? 'eye' : 'eye-open'
|
||||
"
|
||||
/>
|
||||
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'
|
||||
" />
|
||||
</span>
|
||||
</el-form-item>
|
||||
</el-tooltip>
|
||||
@ -89,47 +58,26 @@
|
||||
<span class="svg-container">
|
||||
<svg-icon icon-class="validCode" />
|
||||
</span>
|
||||
<el-input
|
||||
ref="username"
|
||||
v-model="loginForm.code"
|
||||
placeholder="验证码"
|
||||
name="username"
|
||||
type="text"
|
||||
tabindex="3"
|
||||
maxlength="5"
|
||||
autocomplete="off"
|
||||
style="width: 75%"
|
||||
@keyup.enter.native="handleLogin"
|
||||
/>
|
||||
<el-input ref="username" 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>
|
||||
<div
|
||||
class="login-code"
|
||||
style="
|
||||
<div class="login-code" style="
|
||||
cursor: pointer;
|
||||
width: 30%;
|
||||
height: 48px;
|
||||
float: right;
|
||||
background-color: #f0f1f5;
|
||||
"
|
||||
>
|
||||
<img
|
||||
style="
|
||||
">
|
||||
<img style="
|
||||
height: 48px;
|
||||
width: 100%;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 5px;
|
||||
"
|
||||
:src="codeUrl"
|
||||
@click="getCode"
|
||||
>
|
||||
" :src="codeUrl" @click="getCode">
|
||||
</div>
|
||||
|
||||
<el-button
|
||||
:loading="loading"
|
||||
type="primary"
|
||||
style="width: 100%; padding: 12px 20px; margin-bottom: 30px"
|
||||
@click.native.prevent="handleLogin"
|
||||
>
|
||||
<el-button :loading="loading" type="primary" style="width: 100%; padding: 12px 20px; margin-bottom: 30px"
|
||||
@click.native.prevent="handleLogin">
|
||||
<span v-if="!loading">登 录</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
@ -146,11 +94,7 @@
|
||||
<br>
|
||||
<social-sign />
|
||||
</el-dialog>
|
||||
<div
|
||||
id="bottom_layer"
|
||||
class="s-bottom-layer s-isindex-wrap"
|
||||
style="visibility: visible; width: 100%"
|
||||
>
|
||||
<div id="bottom_layer" class="s-bottom-layer s-isindex-wrap" style="visibility: visible; width: 100%">
|
||||
<div class="s-bottom-layer-content">
|
||||
|
||||
<div class="lh">
|
||||
@ -163,11 +107,7 @@
|
||||
<div class="rest_info_tip">
|
||||
<div class="tip-wrapper">
|
||||
<div class="lh tip-item" style="display: none">
|
||||
<a
|
||||
class="text-color"
|
||||
href="https://beian.miit.gov.cn"
|
||||
target="_blank"
|
||||
>
|
||||
<a class="text-color" href="https://beian.miit.gov.cn" target="_blank">
|
||||
沪ICP备XXXXXXXXX号-1
|
||||
</a>
|
||||
</div>
|
||||
@ -177,17 +117,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<GoogleAuth :visible.sync="showGoogleVerifyDialog" :loading="googleVerifying" @confirm="submitGoogleCode"
|
||||
@cancel="showGoogleVerifyDialog = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCodeImg } from '@/api/login'
|
||||
import { needCheckGoogleAuth, checkGoogleCode } from '@/api/admin/sys-user'
|
||||
import moment from 'moment'
|
||||
import SocialSign from './components/SocialSignin'
|
||||
import GoogleAuth from './google_auth'
|
||||
import { setToken } from "@/utils/auth";
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
components: { SocialSign },
|
||||
components: {
|
||||
SocialSign,
|
||||
GoogleAuth
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
codeUrl: '',
|
||||
@ -218,12 +167,15 @@ export default {
|
||||
redirect: undefined,
|
||||
otherQuery: {},
|
||||
currentTime: null,
|
||||
sysInfo: ''
|
||||
sysInfo: '',
|
||||
showGoogleVerifyDialog: false,
|
||||
googleVerifying: false,
|
||||
googleTokenCache: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route: {
|
||||
handler: function(route) {
|
||||
handler: function (route) {
|
||||
const query = route.query
|
||||
if (query) {
|
||||
this.redirect = query.redirect
|
||||
@ -252,10 +204,11 @@ export default {
|
||||
},
|
||||
destroyed() {
|
||||
clearInterval(this.timer)
|
||||
window.removeEventListener('resize', () => {})
|
||||
window.removeEventListener('resize', () => { })
|
||||
// window.removeEventListener('storage', this.afterQRScan)
|
||||
},
|
||||
methods: {
|
||||
setToken,
|
||||
getSystemSetting() {
|
||||
this.$store.dispatch('system/settingDetail').then((ret) => {
|
||||
this.sysInfo = ret
|
||||
@ -300,26 +253,55 @@ export default {
|
||||
this.$refs.password.focus()
|
||||
})
|
||||
},
|
||||
handleLogin() {
|
||||
this.$refs.loginForm.validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true
|
||||
this.$store
|
||||
.dispatch('user/login', this.loginForm)
|
||||
.then(() => {
|
||||
this.$router
|
||||
.push({ path: this.redirect || '/', query: this.otherQuery })
|
||||
.catch(() => {})
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false
|
||||
this.getCode()
|
||||
})
|
||||
} else {
|
||||
console.log('error submit!!')
|
||||
return false
|
||||
async validateForm() {
|
||||
return new Promise((resolve) => {
|
||||
this.$refs.loginForm.validate((valid) => {
|
||||
resolve(valid);
|
||||
});
|
||||
});
|
||||
},
|
||||
async handleLogin() {
|
||||
const valid = await this.validateForm();
|
||||
if (!valid) return;
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const res = await this.$store.dispatch('user/login', {
|
||||
userInfo: this.loginForm,
|
||||
handleResponse: async (res) => {
|
||||
|
||||
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) {
|
||||
return Object.keys(query).reduce((acc, cur) => {
|
||||
@ -328,6 +310,30 @@ export default {
|
||||
}
|
||||
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;
|
||||
// background: #0e6cff;
|
||||
}
|
||||
|
||||
#bottom_layer .lh {
|
||||
display: inline-block;
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
#bottom_layer .lh .emphasize {
|
||||
text-decoration: underline;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#bottom_layer .lh:last-child {
|
||||
margin-left: -2px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#bottom_layer .lh.activity {
|
||||
font-weight: 700;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#bottom_layer a {
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#bottom_layer .text-color {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
#bottom_layer .aria-img {
|
||||
width: 49px;
|
||||
height: 20px;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
|
||||
#bottom_layer a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bottom_layer .s-bottom-layer-content {
|
||||
margin: 0 17px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#bottom_layer .s-bottom-layer-content .auto-transform-line {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#bottom_layer .s-bottom-layer-content .auto-transform-line:first-child {
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
.s-bottom-space {
|
||||
position: static;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
margin: 23px auto 12px;
|
||||
}
|
||||
|
||||
#bottom_layer .open-content-info a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bottom_layer .open-content-info .text-color {
|
||||
color: #626675;
|
||||
}
|
||||
|
||||
.open-content-info {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
}
|
||||
.open-content-info > span {
|
||||
|
||||
.open-content-info>span {
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
.open-content-info > span:hover {
|
||||
|
||||
.open-content-info>span:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.open-content-info .tip-hover-panel {
|
||||
position: absolute;
|
||||
display: none;
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
|
||||
.open-content-info .tip-hover-panel .rest_info_tip {
|
||||
max-width: 560px;
|
||||
padding: 8px 12px 8px 12px;
|
||||
@ -434,29 +459,31 @@ $cursor: #fff;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.open-content-info .tip-hover-panel .rest_info_tip .tip-wrapper {
|
||||
white-space: nowrap;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.open-content-info .tip-hover-panel .rest_info_tip .tip-wrapper .tip-item {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.open-content-info
|
||||
.tip-hover-panel
|
||||
.rest_info_tip
|
||||
.tip-wrapper
|
||||
.tip-item:last-child {
|
||||
|
||||
.open-content-info .tip-hover-panel .rest_info_tip .tip-wrapper .tip-item:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 515px) {
|
||||
.open-content-info {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.open-content-info .tip-hover-panel {
|
||||
right: -16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #0e6cff;
|
||||
margin-bottom: -20px;
|
||||
@ -517,6 +544,7 @@ $cursor: #fff;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
|
||||
.login-time {
|
||||
position: absolute;
|
||||
left: 25px;
|
||||
@ -613,6 +641,7 @@ $cursor: #fff;
|
||||
color: #454545;
|
||||
}
|
||||
}
|
||||
|
||||
$bg: #2d3a4b;
|
||||
$dark_gray: #889aa4;
|
||||
$light_gray: #eee;
|
||||
@ -670,18 +699,22 @@ $light_gray: #eee;
|
||||
.thirdparty-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.login-weaper {
|
||||
width: 100%;
|
||||
padding: 0 30px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.login-main {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.login-left {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.login-border {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user