526 lines
13 KiB
Vue
526 lines
13 KiB
Vue
<template>
|
||
<el-card shadow="never" header="账户安全">
|
||
<div class="bindBxo">
|
||
<div class="title" style="margin-top: 5px;">账号绑定</div>
|
||
<div class="boxView">
|
||
<div class="leftImg">
|
||
<div class="leftIcon wechat" v-if="wechat.open_id=='' || wechat.avatar==''"><i class="el-icon"><sc-icon-WechartRound/></i></div>
|
||
<div class="leftIcon" v-else>
|
||
<el-avatar :size="38" :src="wechat.avatar" />
|
||
</div>
|
||
<div class="nameBox">
|
||
<div class="titleName nameRed" v-if="wechat.open_id==''">绑定微信</div>
|
||
<div class="titleName" v-else>{{wechat.nick_name==""?'-':wechat.nick_name}}</div>
|
||
<div class="msg">绑定微信,用于账号登录,客服咨询及其它消息提醒。</div>
|
||
</div>
|
||
</div>
|
||
<div class="rightBtn">
|
||
<el-button type="primary" :size="size" v-if="wechat.open_id==''" @click="bindWechat">绑 定</el-button>
|
||
<el-button type="primary" :size="size" v-else plain @click="secureDingTalk(1)">解 除</el-button>
|
||
</div>
|
||
</div>
|
||
<div class="boxView">
|
||
<div class="leftImg">
|
||
<div class="leftIcon alipay" v-if="dingTalk.open_id=='' || dingTalk.avatar==''"><i class="el-icon"><sc-icon-DingTalk/></i></div>
|
||
<div class="leftIcon" v-else>
|
||
<el-avatar :size="38" :src="dingTalk.avatar" />
|
||
</div>
|
||
<div class="nameBox">
|
||
<div class="titleName nameRed" v-if="dingTalk.open_id==''">绑定钉钉</div>
|
||
<div class="titleName" v-else>{{dingTalk.nick_name==""?'-':dingTalk.nick_name}}</div>
|
||
<div class="msg">绑定钉钉,用于账号登录。</div>
|
||
</div>
|
||
</div>
|
||
<div class="rightBtn">
|
||
<el-button type="primary" :size="size" v-if="dingTalk.open_id==''" @click="bindDingTalk">绑 定</el-button>
|
||
<el-button type="primary" :size="size" v-else plain @click="secureDingTalk(2)">解 除</el-button>
|
||
</div>
|
||
</div>
|
||
<div class="title">安全登录</div>
|
||
<div class="boxView boxViewCenter">
|
||
<div class="leftBox">允许多地登录</div>
|
||
<div class="rightBox">
|
||
<el-radio-group v-model="multipleLocation" @change="multipleLocationSet">
|
||
<el-radio :label="true">允许多地</el-radio>
|
||
<el-radio :label="false">限制单设备</el-radio>
|
||
</el-radio-group>
|
||
</div>
|
||
</div>
|
||
<div class="boxView boxViewCenter bandTime">
|
||
<div class="leftBox">系统超时安全自动退出时间</div>
|
||
<div class="rightBox">
|
||
<div class="boxInput">
|
||
<el-input onkeyup="value=value.replace(/[^\d]/g,'')" @change="loginTime" controls-position="right" v-model="timeout" placeholder="请输入时间" style="width: 260px">
|
||
<template #suffix>分钟</template>
|
||
</el-input>
|
||
</div>
|
||
<div class="tip">光标移出自动更新退出时间。</div>
|
||
</div>
|
||
</div>
|
||
<div class="title">OA能力</div>
|
||
<div class="boxView boxViewCenter">
|
||
<div class="leftBox">启用阿里企业邮箱功能</div>
|
||
<div class="rightBox">
|
||
<!-- v-model="multipleLocation" @change="multipleLocationSet"-->
|
||
<el-radio-group>
|
||
<el-radio :label="true">启用</el-radio>
|
||
<el-radio :label="false">禁用</el-radio>
|
||
</el-radio-group>
|
||
</div>
|
||
</div>
|
||
<div class="title">使用Passkey</div>
|
||
<div class="boxView passKeyView boxViewCenter">
|
||
<div class="nameBox passkeyView">
|
||
<!-- <el-button type="primary" :size="size" @click="createPasskey">添加passKey</el-button>-->
|
||
<div class="msg">借助 Passkey,你可以使用自己的指纹、面孔、屏锁设置或实体安全密钥登录你的账号。请仅在你自有的设备上设置 Passkey。</div>
|
||
</div>
|
||
</div>
|
||
<div class="boxView passKeyView boxViewCenter">
|
||
<div class="itemMain">
|
||
<div class="boxCom" v-for="item in passKeyList" :key="item">
|
||
<i class="icon"><sc-icon-Fingerprint /></i>
|
||
<span class="name">
|
||
<el-input class="nameInput" v-model="item.alias" @change="passKeyAlias(item)" placeholder="请输入"></el-input>
|
||
</span>
|
||
</div>
|
||
<div class="boxCom" @click="createPasskey">
|
||
<span class="iconBack">
|
||
<i class="icon"><el-icon-Plus/></i>
|
||
</span>
|
||
<span class="name">添加</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
<el-dialog
|
||
v-model="showWechatLogin"
|
||
title="微信绑定"
|
||
:width="500"
|
||
destroy-on-close
|
||
>
|
||
<div class="qrCodeLogin">
|
||
<div class="code_container">
|
||
<object :data="WechatLoginCode" width="430" height="430" type="text/html"></object>
|
||
</div>
|
||
<p class="error" v-if="bind_wechat_error!=''">{{bind_wechat_error}},请先解绑原账号绑定</p>
|
||
<p class="msg">
|
||
请使用微信扫一扫绑定
|
||
</p>
|
||
<div class="qrCodeLogin-result" v-if="isWechatLoginResult">
|
||
<el-result
|
||
icon="success"
|
||
title="绑定成功"
|
||
sub-title="您可以使用微信扫码登录了"
|
||
></el-result>
|
||
</div>
|
||
</div>
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<script>
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
size:'small',
|
||
wechat:{
|
||
avatar:'',
|
||
nick_name:'',
|
||
open_id:''
|
||
},
|
||
dingTalk:{
|
||
avatar:'',
|
||
nick_name:'',
|
||
open_id:'',
|
||
},
|
||
WechatLoginCode:'',
|
||
showWechatLogin:false,
|
||
isWechatLoginResult: false,
|
||
bind_wechat_error:'',
|
||
|
||
multipleLocation:false,
|
||
timeout:'10',
|
||
userInfo:{},
|
||
|
||
passKeyList:[],
|
||
}
|
||
},
|
||
created() {
|
||
// 监听缓存变化 addEventListener
|
||
window.addEventListener('storage',this.wechatStorageChange);
|
||
},
|
||
mounted() {
|
||
const userInfo = this.$TOOL.data.get("USER_INFO");
|
||
this.userInfo = userInfo;
|
||
|
||
this.getUserInfo();
|
||
this.getSso();
|
||
this.getTimeOut();
|
||
this.getPassKeyList();
|
||
// 获取新消息
|
||
// this.$socketApi.getSock(this.getWsResult);
|
||
},
|
||
beforeUnmount() {
|
||
window.removeEventListener('storage', this.wechatStorageChange);
|
||
},
|
||
watch:{
|
||
|
||
},
|
||
methods:{
|
||
async getTimeOut() {
|
||
const res = await this.$API.user.timeoutGet.post();
|
||
if (res.code == 200) {
|
||
this.timeout = res.data.options && res.data.options.timeout?res.data.options.timeout:this.timeout;
|
||
}
|
||
},
|
||
async loginTime(e) {
|
||
const res = await this.$API.user.timeoutConfig.post({timeout:e});
|
||
if (res.code == 200) {
|
||
this.$message.success('设置成功');
|
||
}
|
||
},
|
||
|
||
async getSso(){
|
||
const res = await this.$API.system.sso.get.post();
|
||
if(res.code == 200){
|
||
if(res.data.options){
|
||
this.multipleLocation = res.data.options.is_sso_login;
|
||
}
|
||
}
|
||
},
|
||
async multipleLocationSet(){
|
||
const res = await this.$API.system.sso.setup.post({is_sso_login:this.multipleLocation});
|
||
if(res.code == 200){
|
||
this.$message.success('配置成功');
|
||
}
|
||
},
|
||
|
||
getWsResult(res){
|
||
if(res.type == 7){
|
||
this.isWechatLoginResult = true;
|
||
setTimeout(()=>{
|
||
this.showWechatLogin = false;
|
||
this.getUserInfo();
|
||
},2000)
|
||
}
|
||
if(res.type == 12){
|
||
this.bind_wechat_error = res.msg;
|
||
}
|
||
},
|
||
async bindWechat(){
|
||
this.isWechatLoginResult = false;
|
||
const res = await this.$API.auth.bindWechat.post();
|
||
if(res.code == 200){
|
||
const url = JSON.parse(JSON.stringify(res.data.qrcode));
|
||
localStorage.setItem('bindWechat','2');
|
||
this.WechatLoginCode = url;
|
||
this.showWechatLogin = true;
|
||
}
|
||
},
|
||
async bindDingTalk(){
|
||
const res = await this.$API.auth.bindDingTalk.post();
|
||
if(res.code == 200){
|
||
const url = JSON.parse(JSON.stringify(res.data.redirect));
|
||
localStorage.setItem('bindDingTalk','2');
|
||
window.open(url);
|
||
}
|
||
},
|
||
wechatStorageChange(e){
|
||
let dingTalk = localStorage.getItem('bindDingTalk');
|
||
let wechat = localStorage.getItem('bindWechat');
|
||
if(e.key == 'DINGTALK_LOGIN_MESSAGE'){
|
||
if(dingTalk == 2 && typeof e.newValue =="string" && e.newValue!=''){
|
||
this.dingTalkBind(e.newValue)
|
||
}
|
||
localStorage.removeItem("DINGTALK_LOGIN_MESSAGE");
|
||
}
|
||
|
||
if(e.key == 'WECHAT_LOGIN_MESSAGE'){
|
||
if(wechat == 2 && typeof e.newValue =="string" && e.newValue!=''){
|
||
this.wechatBind(e.newValue);
|
||
}
|
||
localStorage.removeItem("WECHAT_LOGIN_MESSAGE");
|
||
}
|
||
},
|
||
wechatBind(){
|
||
|
||
},
|
||
async dingTalkBind(e){
|
||
let item = JSON.parse(e);
|
||
let params = {code:item.code,state:item.state}
|
||
const res = await this.$API.auth.bindDingTalkUser.post(params);
|
||
if(res.code == 200){
|
||
await this.getUserInfo()
|
||
}
|
||
},
|
||
async secureDingTalk(num){
|
||
let params = {app_type:num}
|
||
const res = await this.$API.auth.unbindUser.post(params);
|
||
if(res.code == 200){
|
||
await this.getUserInfo();
|
||
}
|
||
},
|
||
async getUserInfo(){
|
||
const res = await this.$API.user.getInfo.post();
|
||
if(res.code == 200){
|
||
this.wechat={
|
||
avatar:'',
|
||
nick_name:'',
|
||
open_id:''
|
||
}
|
||
this.dingTalk={
|
||
avatar:'',
|
||
nick_name:'',
|
||
open_id:''
|
||
}
|
||
if(res.data && res.data.length){
|
||
res.data.forEach(em=>{
|
||
if(em.app_type == 1){
|
||
this.wechat = em;
|
||
}
|
||
if(em.app_type ==2){
|
||
this.dingTalk = em;
|
||
}
|
||
})
|
||
}
|
||
}
|
||
},
|
||
|
||
// 获取指纹列表
|
||
async getPassKeyList() {
|
||
const res = await this.$API.system.user.publishList.post();
|
||
if(res.code == 200){
|
||
this.passKeyList = res.data;
|
||
}
|
||
},
|
||
// 修改指纹名称
|
||
async passKeyAlias(em) {
|
||
let params = {
|
||
id:em.id,
|
||
alias:em.alias
|
||
}
|
||
await this.$API.system.user.renameAlias.post(params);
|
||
},
|
||
|
||
// 创建通行秘钥
|
||
async createPasskey() {
|
||
try {
|
||
const res = await this.$API.system.user.generateRegistration.post();
|
||
const publicKey= {
|
||
challenge:Uint8Array.from(res.data.challenge),
|
||
rp:{
|
||
name:res.data.rp.name,
|
||
id:res.data.rp.id
|
||
},
|
||
user:{
|
||
id:Uint8Array.from(res.data.user.id),
|
||
name:res.data.user.name,
|
||
displayName:res.data.user.displayName,
|
||
},
|
||
pubKeyCredParams:res.data.pubKeyCredParams,
|
||
authenticatorSelection:{
|
||
authenticatorAttachment:'',
|
||
userVerification:res.data.authenticatorSelection.userVerification,
|
||
},
|
||
timeout:res.data.timeout,
|
||
attestation:res.data.attestation
|
||
};
|
||
const credential = await navigator.credentials.create({ publicKey });
|
||
|
||
await this.storeCredential(credential);
|
||
} catch (error) {
|
||
this.$message.warning('创建通行秘钥失败');
|
||
}
|
||
},
|
||
async storeCredential(credential){
|
||
const params = {
|
||
id: credential.id,
|
||
rawId: this.bufferToBase64URL(credential.rawId),
|
||
type: credential.type,
|
||
response: {
|
||
clientDataJSON: this.bufferToBase64URL(credential.response.clientDataJSON),
|
||
attestationObject: this.bufferToBase64URL(credential.response.attestationObject),
|
||
},
|
||
authenticatorAttachment: credential.authenticatorAttachment,
|
||
}
|
||
const res = await this.$API.system.user.verifyResponse.post(params);
|
||
if(res.code == 200){
|
||
await this.getPassKeyList();
|
||
}
|
||
},
|
||
bufferToBase64URL(buffer) {
|
||
return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)))
|
||
.replace(/\+/g, "-")
|
||
.replace(/\//g, "_")
|
||
.replace(/=/g, "");
|
||
},
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.bindBxo{
|
||
padding: 0 10px;
|
||
.title{
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
margin: 25px 0 10px 0;
|
||
}
|
||
}
|
||
.boxView{
|
||
width: 460px;
|
||
display: flex;
|
||
align-items: end;
|
||
justify-content: space-between;
|
||
margin-top: 10px;
|
||
margin-left: 5px;
|
||
.leftImg{
|
||
display: flex;
|
||
align-items: center;
|
||
.leftIcon{
|
||
width: 40px;
|
||
height: 40px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-items: center;
|
||
margin-right: 10px;
|
||
.el-icon{
|
||
font-size: 34px;
|
||
}
|
||
}
|
||
.titleName{
|
||
margin-bottom: 5px;
|
||
font-size: var(--el-font-size-base);
|
||
}
|
||
.nameRed{
|
||
color: var(--el-color-danger);
|
||
}
|
||
.msg{
|
||
font-size: var(--el-font-size-extra-small);
|
||
color: #837e7e;
|
||
}
|
||
}
|
||
}
|
||
.passKeyView{
|
||
width: 100%;
|
||
.itemMain{
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: flex-start;
|
||
margin-top: 10px;
|
||
.boxCom{
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
width: 80px;
|
||
cursor: pointer;
|
||
.icon{
|
||
text-align: center;
|
||
width: 40px;
|
||
height: 40px;
|
||
}
|
||
.iconBack{
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
background: #E9E9E9;
|
||
font-size: 14px;
|
||
display: flow;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 10px;
|
||
.icon{
|
||
width: 14px;height: 14px;font-size: 12px;
|
||
}
|
||
}
|
||
.name{
|
||
height: 30px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 14px;
|
||
color: var(--el-input-text-color);
|
||
}
|
||
.nameInput{
|
||
text-align: center;
|
||
}
|
||
.el-input{
|
||
text-align: center;
|
||
border: 0;
|
||
|
||
::v-deep .el-input__wrapper{
|
||
border: 0;
|
||
padding: 0;
|
||
box-shadow:none;
|
||
}
|
||
::v-deep .el-input__inner{
|
||
text-align: center;
|
||
}
|
||
}
|
||
.el-input:hover ::v-deep .el-input__wrapper{
|
||
//border-bottom: 1px solid var(--el-input-border-color);
|
||
border-radius: 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.boxViewCenter{
|
||
align-items: center;
|
||
.leftBox{
|
||
font-size: var(--el-font-size-base);
|
||
}
|
||
}
|
||
.bandTime{
|
||
align-items: baseline;
|
||
.tip{
|
||
margin-top: 6px;
|
||
color: #999;
|
||
}
|
||
}
|
||
.qrCodeLogin {
|
||
text-align: center;
|
||
position: relative;
|
||
padding: 0 0 20px 0;
|
||
}
|
||
.code_container{
|
||
width: 430px;
|
||
height:430px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.qrCodeLogin img.qrCode {
|
||
background: #fff;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.qrCodeLogin .error{
|
||
color: var(--el-color-error);
|
||
}
|
||
.qrCodeLogin p.msg {
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.qrCodeLogin .qrCodeLogin-result {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
text-align: center;
|
||
background: var(--el-mask-color);
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
}
|
||
.passkeyView{
|
||
.msg{
|
||
margin-top: 10px;
|
||
color: var(--el-color-warning);
|
||
}
|
||
}
|
||
</style>
|