Files
webs/power_leasing/src/views/account/securitySettings.vue

1166 lines
32 KiB
Vue
Raw Normal View History

<template>
<div class="security-settings" v-loading="statusLoading">
<div class="security-item-wrapper">
<div class="security-item">
<div class="security-left">
<div class="security-icon">
<i class="el-icon-lock"></i>
</div>
<div class="security-info">
<div class="security-title">双重验证</div>
<p class="security-desc">用于登录帐户结算订单提现修改登录密码等涉及账户安全的重要操作</p>
</div>
</div>
<div class="security-right">
<span class="security-status" :class="getStatusClass">
{{ getStatusText }}
</span>
<el-button
:type="getButtonType"
@click="handleButtonClick"
:loading="loading"
class="security-btn"
>
{{ getButtonText }}
</el-button>
</div>
</div>
<div class="security-divider"></div>
</div>
<!-- 第一步显示二维码和密钥 -->
<el-dialog
title="开启双重验证 - 步骤 1/2"
:visible.sync="step1Visible"
width="600px"
:close-on-click-modal="false"
@close="handleStep1Close"
>
<div class="step1-content">
<div class="instruction-text">
<p>请使用您手机上的谷歌身份验证器 (Google Authenticator) 或其它兼容应用程序扫描下方二维码也可手动输入以下密钥</p>
</div>
<div class="qr-section">
<div class="qr-code-wrapper" v-if="qrCodeUrl">
<img :src="getQrCodeSrc" alt="二维码" class="qr-code" />
</div>
<div v-else class="qr-loading">
<i class="el-icon-loading"></i>
<span>加载中...</span>
</div>
</div>
<div class="secret-key-section">
<div class="secret-key-label">或手动输入密钥</div>
<div class="secret-key-input-group">
<el-input
v-model="secretKey"
readonly
class="secret-key-input"
/>
<el-button
type="primary"
@click="handleCopySecret"
:disabled="!secretKey"
>
复制
</el-button>
</div>
</div>
<div class="warning-box">
<i class="el-icon-warning"></i>
<div class="warning-text">
<p>请妥善保存密钥避免被盗或丢失如遇手机丢失等情况可通过该密钥恢复您的谷歌验证如密钥丢失需要提交工单通过人工客服重置处理时间需7天</p>
</div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="step1Visible = false">取消</el-button>
<el-button type="primary" @click="handleNextToStep2" :disabled="!qrCodeUrl || !secretKey">
下一步
</el-button>
</span>
</el-dialog>
<!-- 第二步验证 -->
<el-dialog
title="开启双重验证 - 步骤 2/2"
:visible.sync="step2Visible"
width="500px"
:close-on-click-modal="false"
@close="handleStep2Close"
>
<el-form
ref="verifyForm"
:model="verifyForm"
:rules="verifyRules"
label-position="top"
>
<el-form-item label="登录密码" prop="password">
<el-input
v-model="verifyForm.password"
type="password"
placeholder="请输入密码8-32位"
show-password
clearable
/>
<!-- 密码规则提示 -->
<div class="password-tip">
<i class="el-icon-info"></i>
<span>密码需包含大小写字母数字和特殊字符长度8-32</span>
</div>
</el-form-item>
<el-form-item label="邮箱验证码" prop="emailCode">
<div class="code-input-group">
<el-input
v-model="verifyForm.emailCode"
placeholder="请输入邮箱验证码"
class="code-input"
maxlength="10"
clearable
/>
<el-button
type="primary"
@click="handleSendEmailCode"
:loading="sendingCode"
:disabled="countdown > 0"
>
{{ countdown > 0 ? `${countdown}秒后重试` : '发送验证码' }}
</el-button>
</div>
<div class="help-link">
<a href="javascript:void(0)" @click="handleCannotGetCode">无法获取验证码?</a>
</div>
</el-form-item>
<el-form-item label="谷歌验证码" prop="googleCode">
<el-input
v-model="verifyForm.googleCode"
placeholder="请输入6位动态口令"
maxlength="6"
@input="handleGoogleCodeInput"
/>
<div class="help-link">
<a href="javascript:void(0)" @click="handleCannotGetGoogleCode">无法获取验证码?</a>
</div>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="handleBackToStep1">上一步</el-button>
<el-button type="primary" @click="handleConfirm" :loading="submitting">
确定
</el-button>
</span>
</el-dialog>
<!-- 关闭双重验证弹窗 -->
<el-dialog
title="关闭双重验证"
:visible.sync="closeDialogVisible"
width="500px"
:close-on-click-modal="false"
@close="handleCloseDialogClose"
>
<el-form
ref="closeForm"
:model="closeForm"
:rules="closeRules"
label-position="top"
>
<el-form-item label="邮箱验证码" prop="emailCode">
<div class="code-input-group">
<el-input
v-model="closeForm.emailCode"
placeholder="请输入邮箱验证码"
class="code-input"
maxlength="10"
clearable
/>
<el-button
type="primary"
@click="handleSendCloseEmailCode"
:loading="sendingCloseCode"
:disabled="closeCountdown > 0"
>
{{ closeCountdown > 0 ? `${closeCountdown}秒后重试` : '发送验证码' }}
</el-button>
</div>
</el-form-item>
<el-form-item label="谷歌验证码" prop="googleCode">
<el-input
v-model="closeForm.googleCode"
placeholder="请输入6位动态口令"
maxlength="6"
@input="handleCloseGoogleCodeInput"
clearable
/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="closeDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirmClose" :loading="closing">
确定
</el-button>
</span>
</el-dialog>
<!-- 开启双重验证弹窗 -->
<el-dialog
title="开启双重验证"
:visible.sync="openDialogVisible"
width="500px"
:close-on-click-modal="false"
@close="handleOpenDialogClose"
>
<el-form
ref="openForm"
:model="openForm"
:rules="openRules"
label-position="top"
>
<el-form-item label="邮箱验证码" prop="emailCode">
<div class="code-input-group">
<el-input
v-model="openForm.emailCode"
placeholder="请输入邮箱验证码"
class="code-input"
maxlength="10"
clearable
/>
<el-button
type="primary"
@click="handleSendOpenEmailCode"
:loading="sendingOpenCode"
:disabled="openCountdown > 0"
>
{{ openCountdown > 0 ? `${openCountdown}秒后重试` : '发送验证码' }}
</el-button>
</div>
</el-form-item>
<el-form-item label="谷歌验证码" prop="googleCode">
<el-input
v-model="openForm.googleCode"
placeholder="请输入6位动态口令"
maxlength="6"
@input="handleOpenGoogleCodeInput"
clearable
/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="openDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirmOpen" :loading="opening">
确定
</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { getBindInfo, bindGoogle, sendOpenGoogleCode,getGoogleStatus,closeStepTwo,sendCloseGoogleCode,openStepTwo } from '../../api/verification'
import { rsaEncrypt } from '../../utils/rsaEncrypt'
export default {
name: 'SecuritySettings',
data() {
/**
* 密码格式验证
* 8-32包含大小写字母数字和特殊字符
*/
const validatePassword = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入密码'))
return
}
// 密码验证正则8-32位包含大小写字母、数字和特殊字符@#¥%……&.*
const regexPassword = /^(?!.*[\u4e00-\u9fa5])(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\W_]+$)(?![a-z0-9]+$)(?![a-z\W_]+$)(?![0-9\W_]+$)[a-zA-Z0-9\W_]{8,32}$/
if (!regexPassword.test(value)) {
callback(new Error('密码应包含大小写字母、数字和特殊字符长度8-32位'))
return
}
callback()
}
return {
isEnabled: false, // 是否已开启双重验证
loading: false,
statusLoading: false, // 状态查询的 loading
step1Visible: false,
step2Visible: false,
closeDialogVisible: false, // 关闭双重验证弹窗
openDialogVisible: false, // 开启双重验证弹窗
qrCodeUrl: '',
secretKey: '',
sendingCode: false,
countdown: 0,
countdownTimer: null,
sendingCloseCode: false, // 发送关闭验证码的 loading
closeCountdown: 0, // 关闭验证码倒计时
closeCountdownTimer: null, // 关闭验证码倒计时定时器
sendingOpenCode: false, // 发送开启验证码的 loading
openCountdown: 0, // 开启验证码倒计时
openCountdownTimer: null, // 开启验证码倒计时定时器
closing: false, // 关闭双重验证的 loading
opening: false, // 开启双重验证的 loading
submitting: false,
verifyForm: {
password: '',
emailCode: '',
googleCode: ''
},
closeForm: {
emailCode: '',
googleCode: ''
},
openForm: {
emailCode: '',
googleCode: ''
},
verifyRules: {
password: [
{ required: true, validator: validatePassword, trigger: 'blur' }
],
emailCode: [
{ required: true, message: '请输入邮箱验证码', trigger: 'blur' },
{ min: 1, max: 10, message: '验证码长度为1-10位', trigger: 'blur' }
],
googleCode: [
{ required: true, message: '请输入谷歌验证码', trigger: 'blur' },
{ pattern: /^\d{6}$/, message: '请输入6位数字', trigger: 'blur' }
]
},
closeRules: {
emailCode: [
{ required: true, message: '请输入邮箱验证码', trigger: 'blur' },
{ min: 1, max: 10, message: '验证码长度为1-10位', trigger: 'blur' }
],
googleCode: [
{ required: true, message: '请输入谷歌验证码', trigger: 'blur' },
{ pattern: /^\d{6}$/, message: '请输入6位数字', trigger: 'blur' }
]
},
openRules: {
emailCode: [
{ required: true, message: '请输入邮箱验证码', trigger: 'blur' },
{ min: 1, max: 10, message: '验证码长度为1-10位', trigger: 'blur' }
],
googleCode: [
{ required: true, message: '请输入谷歌验证码', trigger: 'blur' },
{ pattern: /^\d{6}$/, message: '请输入6位数字', trigger: 'blur' }
]
},
googleStatus: 1 // 谷歌验证状态0 开启1 未绑定2 关闭
}
},
computed: {
/**
* 获取二维码图片源处理 Base64 格式
*/
getQrCodeSrc() {
if (!this.qrCodeUrl) return ''
// 如果已经是完整的 data URI直接返回
if (this.qrCodeUrl.startsWith('data:')) {
return this.qrCodeUrl
}
// 如果是 Base64 字符串,添加前缀
return `data:image/png;base64,${this.qrCodeUrl}`
},
/**
* 获取状态显示文字
* 0 开启1 未绑定2 关闭
*/
getStatusText() {
switch (this.googleStatus) {
case 0:
return '已开启' // 0 开启 -> 显示已开启
case 1:
return '未绑定' // 1 未绑定
case 2:
return '已关闭' // 2 关闭 -> 显示已关闭
default:
return '未绑定'
}
},
/**
* 获取按钮文字
* 0 开启1 未绑定2 关闭
*/
getButtonText() {
switch (this.googleStatus) {
case 0:
return '关闭' // 0 开启 -> 按钮显示关闭
case 1:
return '设置' // 1 未绑定 -> 按钮显示设置
case 2:
return '开启' // 2 关闭 -> 按钮显示开启
default:
return '设置'
}
},
/**
* 获取状态样式类
* 0 开启1 未绑定2 关闭
*/
getStatusClass() {
return {
'status-enabled': this.googleStatus === 0, // 状态0开启显示绿色
'status-bound': this.googleStatus === 2 // 状态2关闭可以有不同的样式
}
},
/**
* 获取按钮类型
* 0 开启1 未绑定2 关闭
*/
getButtonType() {
switch (this.googleStatus) {
case 0:
return 'danger' // 0 开启 -> 关闭按钮使用危险色(红色)
case 1:
return 'primary' // 1 未绑定 -> 设置按钮使用主色(蓝色)
case 2:
return 'primary' // 2 关闭 -> 开启按钮使用主色(蓝色)
default:
return 'primary'
}
}
},
mounted() {
this.check2FAStatus()
},
beforeDestroy() {
if (this.countdownTimer) {
clearInterval(this.countdownTimer)
}
if (this.closeCountdownTimer) {
clearInterval(this.closeCountdownTimer)
}
if (this.openCountdownTimer) {
clearInterval(this.openCountdownTimer)
}
},
methods: {
/**
* 检查双重验证状态
* 返回值0 开启1 未绑定2 关闭
*/
async check2FAStatus() {
this.statusLoading = true
try {
const res = await getGoogleStatus()
if (res && (res.code === 0 || res.code === 200)) {
const status = res.data?.status ?? res.data ?? 1
// 0: 开启
// 1: 未绑定
// 2: 关闭
this.googleStatus = status
this.isEnabled = status === 0 // 兼容旧逻辑
} else {
// 如果接口调用失败,默认未绑定
this.googleStatus = 1
this.isEnabled = false
}
} catch (e) {
console.error('查询谷歌绑定状态失败', e)
// 出错时默认未绑定
this.googleStatus = 1
this.isEnabled = false
} finally {
this.statusLoading = false
}
},
/**
* 处理按钮点击
* 0 开启1 未绑定2 关闭
*/
handleButtonClick() {
switch (this.googleStatus) {
case 0:
// 0 开启 -> 点击关闭
this.handleDisable2FA()
break
case 1:
// 1 未绑定 -> 点击设置(开启)
this.handleEnable2FA()
break
case 2:
// 2 关闭 -> 点击开启
this.handleEnable2FA()
break
default:
this.handleEnable2FA()
}
},
/**
* 开启双重验证
* 状态 1未绑定需要先获取二维码和密钥
* 状态 2已关闭直接显示开启验证弹窗
*/
async handleEnable2FA() {
// 如果是未绑定状态,需要先获取二维码和密钥
if (this.googleStatus === 1) {
this.loading = true
try {
const res = await getBindInfo()
if (res && (res.code === 0 || res.code === 200)) {
// img 是 Base64 格式的二维码图片
this.qrCodeUrl = res.data?.img || ''
// secret 是返回的密钥
this.secretKey = res.data?.secret || ''
console.log('getBindInfo 返回数据:', res.data) // 调试用
console.log('保存的 secretKey:', this.secretKey) // 调试用
if (this.qrCodeUrl || this.secretKey) {
this.step1Visible = true
} else {
this.$message.error('获取绑定信息失败,请稍后重试')
}
} else {
this.$message.error(res?.message || res?.msg || '获取绑定信息失败')
}
} catch (e) {
console.error('获取绑定信息失败', e)
this.$message.error('获取绑定信息失败,请稍后重试')
} finally {
this.loading = false
}
} else {
// 如果是已关闭状态,直接显示开启验证弹窗
this.openDialogVisible = true
}
},
/**
* 关闭双重验证 - 显示弹窗
*/
handleDisable2FA() {
this.closeDialogVisible = true
},
/**
* 发送关闭双重验证的邮箱验证码
*/
async handleSendCloseEmailCode() {
if (this.closeCountdown > 0) return
this.sendingCloseCode = true
try {
const res = await sendCloseGoogleCode() // 不需要参数
if (res && (res.code === 0 || res.code === 200)) {
this.$message.success('验证码已发送到您的邮箱')
this.startCloseCountdown()
} else {
this.$message.error(res?.message || res?.msg || '发送验证码失败')
}
} catch (e) {
console.error('发送验证码失败', e)
this.$message.error('发送验证码失败,请稍后重试')
} finally {
this.sendingCloseCode = false
}
},
/**
* 开始关闭验证码倒计时
*/
startCloseCountdown() {
this.closeCountdown = 60
this.closeCountdownTimer = setInterval(() => {
this.closeCountdown--
if (this.closeCountdown <= 0) {
clearInterval(this.closeCountdownTimer)
this.closeCountdownTimer = null
}
}, 1000)
},
/**
* 关闭双重验证弹窗的谷歌验证码输入仅数字
*/
handleCloseGoogleCodeInput(val) {
this.closeForm.googleCode = val.replace(/\D/g, '').slice(0, 6)
},
/**
* 确认关闭双重验证
*/
async handleConfirmClose() {
try {
const valid = await this.$refs.closeForm.validate()
if (!valid) return
this.closing = true
const params = {
eCode: this.closeForm.emailCode, // 邮箱验证码
gCode: this.closeForm.googleCode // 谷歌验证码
}
const res = await closeStepTwo(params)
if (res && (res.code === 0 || res.code === 200)) {
this.$message.success('双重验证已关闭')
this.closeDialogVisible = false
this.googleStatus = 2 // 设置为已绑定,关闭
this.isEnabled = false
this.handleCloseDialogClose()
// 刷新状态
this.check2FAStatus()
} else {
this.$message.error(res?.message || res?.msg || '关闭失败,请检查输入信息')
}
} catch (e) {
console.error('关闭双重验证失败', e)
this.$message.error('关闭失败,请稍后重试')
} finally {
this.closing = false
}
},
/**
* 关闭双重验证弹窗关闭时的处理
*/
handleCloseDialogClose() {
this.closeForm = {
emailCode: '',
googleCode: ''
}
this.$refs.closeForm && this.$refs.closeForm.clearValidate()
},
/**
* 发送开启双重验证的邮箱验证码
*/
async handleSendOpenEmailCode() {
if (this.openCountdown > 0) return
this.sendingOpenCode = true
try {
const res = await sendOpenGoogleCode() // 不需要参数
if (res && (res.code === 0 || res.code === 200)) {
this.$message.success('验证码已发送到您的邮箱')
this.startOpenCountdown()
} else {
this.$message.error(res?.message || res?.msg || '发送验证码失败')
}
} catch (e) {
console.error('发送验证码失败', e)
this.$message.error('发送验证码失败,请稍后重试')
} finally {
this.sendingOpenCode = false
}
},
/**
* 开始开启验证码倒计时
*/
startOpenCountdown() {
this.openCountdown = 60
this.openCountdownTimer = setInterval(() => {
this.openCountdown--
if (this.openCountdown <= 0) {
clearInterval(this.openCountdownTimer)
this.openCountdownTimer = null
}
}, 1000)
},
/**
* 开启双重验证弹窗的谷歌验证码输入仅数字
*/
handleOpenGoogleCodeInput(val) {
this.openForm.googleCode = val.replace(/\D/g, '').slice(0, 6)
},
/**
* 确认开启双重验证
*/
async handleConfirmOpen() {
try {
const valid = await this.$refs.openForm.validate()
if (!valid) return
this.opening = true
const params = {
eCode: this.openForm.emailCode, // 邮箱验证码
gCode: this.openForm.googleCode // 谷歌验证码
}
const res = await openStepTwo(params)
if (res && (res.code === 0 || res.code === 200)) {
this.$message.success('双重验证已开启')
this.openDialogVisible = false
this.googleStatus = 0 // 设置为已绑定,开启
this.isEnabled = true
this.handleOpenDialogClose()
// 刷新状态
this.check2FAStatus()
} else {
this.$message.error(res?.message || res?.msg || '开启失败,请检查输入信息')
}
} catch (e) {
console.error('开启双重验证失败', e)
this.$message.error('开启失败,请稍后重试')
} finally {
this.opening = false
}
},
/**
* 开启双重验证弹窗关闭时的处理
*/
handleOpenDialogClose() {
this.openForm = {
emailCode: '',
googleCode: ''
}
this.$refs.openForm && this.$refs.openForm.clearValidate()
},
/**
* 复制密钥
*/
handleCopySecret() {
if (!this.secretKey) return
const input = document.createElement('input')
input.value = this.secretKey
document.body.appendChild(input)
input.select()
try {
document.execCommand('copy')
this.$message.success('密钥已复制到剪贴板')
} catch (e) {
this.$message.error('复制失败,请手动复制')
}
document.body.removeChild(input)
},
/**
* 第一步关闭
* 注意不要清空 secretKey因为第二步提交时需要用到
* 只有在用户真正取消点击取消按钮或 X时才清空
*/
handleStep1Close() {
this.qrCodeUrl = ''
// 不清空 secretKey因为第二步提交时需要用到
// 如果用户点击取消,会在 handleStep2Close 中清空
},
/**
* 进入第二步
*/
handleNextToStep2() {
if (!this.qrCodeUrl && !this.secretKey) {
this.$message.warning('请先获取二维码或密钥')
return
}
this.step1Visible = false
this.step2Visible = true
},
/**
* 返回第一步
*/
handleBackToStep1() {
this.step2Visible = false
this.step1Visible = true
},
/**
* 第二步关闭
*/
handleStep2Close() {
this.verifyForm = {
password: '',
emailCode: '',
googleCode: ''
}
this.$refs.verifyForm && this.$refs.verifyForm.clearValidate()
},
/**
* 发送邮箱验证码
*/
async handleSendEmailCode() {
if (this.countdown > 0) return
this.sendingCode = true
try {
const res = await sendOpenGoogleCode()
if (res && (res.code === 0 || res.code === 200)) {
this.$message.success('验证码已发送到您的邮箱')
this.startCountdown()
} else {
this.$message.error(res?.message || res?.msg || '发送验证码失败')
}
} catch (e) {
console.error('发送验证码失败', e)
this.$message.error('发送验证码失败,请稍后重试')
} finally {
this.sendingCode = false
}
},
/**
* 开始倒计时
*/
startCountdown() {
this.countdown = 60
this.countdownTimer = setInterval(() => {
this.countdown--
if (this.countdown <= 0) {
clearInterval(this.countdownTimer)
this.countdownTimer = null
}
}, 1000)
},
/**
* 双重验证码输入仅数字
*/
handleGoogleCodeInput(val) {
this.verifyForm.googleCode = val.replace(/\D/g, '').slice(0, 6)
},
/**
* 确认提交
*/
async handleConfirm() {
try {
const valid = await this.$refs.verifyForm.validate()
if (!valid) return
// 检查密钥是否存在
if (!this.secretKey) {
this.$message.warning('密钥不存在,请重新获取')
return
}
// 检查密码是否存在
if (!this.verifyForm.password) {
this.$message.warning('请输入密码')
return
}
this.submitting = true
// 对密码进行 RSA 加密
const encryptedPassword = await rsaEncrypt(this.verifyForm.password)
if (!encryptedPassword) {
this.$message.error('密码加密失败,请稍后重试')
this.submitting = false
return
}
const params = {
eCode: this.verifyForm.emailCode, // 邮箱验证码
gCode: this.verifyForm.googleCode, // 谷歌验证码
pwd: encryptedPassword, // RSA 加密后的密码
secret: this.secretKey // 上一步弹窗的密钥getBindInfo 返回的 secret
}
console.log('提交参数:', params) // 调试用,确认 secret 是否正确
const res = await bindGoogle(params)
if (res && (res.code === 0 || res.code === 200)) {
this.$message.success('双重验证已成功开启')
this.step2Visible = false
this.googleStatus = 0 // 设置为已绑定,开启
this.isEnabled = true
this.handleStep2Close()
} else {
this.$message.error(res?.message || res?.msg || '绑定失败,请检查输入信息')
}
} catch (e) {
console.error('绑定失败', e)
} finally {
this.submitting = false
}
},
/**
* 无法获取验证码
*/
handleCannotGetCode() {
this.$message.info('请检查邮箱垃圾箱,或联系客服')
},
/**
* 无法获取谷歌验证码
*/
handleCannotGetGoogleCode() {
this.$message.info('请确保已正确扫描二维码或输入密钥,并检查时间同步')
}
}
}
</script>
<style scoped>
.security-settings {
padding: 0;
}
.security-item-wrapper {
background: #fff;
border-radius: 8px;
overflow: visible; /* 改为 visible允许内容溢出 */
width: 100%;
}
.security-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24px;
width: 100%;
min-width: 1000px; /* 设置最小宽度,确保有足够空间 */
}
.security-left {
display: flex;
align-items: flex-start;
gap: 16px;
flex: 1;
min-width: 0; /* 允许 flex 子元素收缩 */
}
.security-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.security-icon i {
font-size: 24px;
color: #667eea;
}
.security-info {
flex: 1;
text-align: left;
min-width: 700px; /* 设置最小宽度,给文字更多显示空间 */
flex-shrink: 0; /* 防止被压缩 */
}
.security-title {
font-size: 16px;
font-weight: 600;
color: #2c3e50;
margin: 0 0 8px 0;
text-align: left;
}
.security-desc {
margin: 0;
font-size: 14px;
color: #909399;
line-height: 1.6;
text-align: left;
}
.security-right {
display: flex;
align-items: center;
gap: 16px;
flex-shrink: 0;
}
.security-status {
font-size: 13px;
color: #f56c6c;
font-weight: 500;
}
.security-status.status-enabled {
color: #67c23a;
}
.security-btn {
min-width: 70px;
padding: 7px 14px;
font-size: 13px;
border-radius: 6px;
transition: all 0.3s ease;
}
.security-btn:hover {
transform: translateY(-1px);
}
.security-btn.el-button--primary:hover {
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}
.security-btn.el-button--danger:hover {
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
}
.security-divider {
height: 1px;
background: #e4e7ed;
margin: 0 24px;
}
/* 第一步对话框样式 */
.step1-content {
padding: 0 8px;
}
.instruction-text {
margin-bottom: 24px;
color: #606266;
font-size: 14px;
line-height: 1.6;
}
.qr-section {
display: flex;
justify-content: center;
margin-bottom: 24px;
}
.qr-code-wrapper {
padding: 16px;
background: #f5f7fa;
border-radius: 8px;
border: 1px solid #e4e7ed;
}
.qr-code {
width: 200px;
height: 200px;
display: block;
}
.qr-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px;
color: #909399;
gap: 12px;
}
.qr-loading i {
font-size: 32px;
}
.secret-key-section {
margin-bottom: 24px;
}
.secret-key-label {
margin-bottom: 12px;
font-size: 14px;
color: #606266;
font-weight: 500;
}
.secret-key-input-group {
display: flex;
gap: 12px;
}
.secret-key-input {
flex: 1;
}
.warning-box {
display: flex;
gap: 12px;
padding: 16px;
background: #fef0f0;
border: 1px solid #fde2e2;
border-radius: 6px;
color: #f56c6c;
}
.warning-box i {
font-size: 20px;
flex-shrink: 0;
margin-top: 2px;
}
.warning-text {
flex: 1;
font-size: 13px;
line-height: 1.6;
margin: 0;
}
/* 第二步对话框样式 */
/* 调整弹窗内容区域的 padding使左右对称 */
.security-settings :deep(.el-dialog__body) {
padding: 20px 24px;
text-align: left;
}
/* 第二步弹窗中 label 文字左对齐 */
.security-settings :deep(.el-form--label-top .el-form-item__label) {
text-align: left;
padding: 0 0 8px 0;
justify-content: flex-start;
}
/* 确保表单项内容左对齐 */
.security-settings :deep(.el-form--label-top .el-form-item__content) {
text-align: left;
}
/* 确保输入框左对齐 */
.security-settings :deep(.el-input) {
text-align: left;
}
.security-settings :deep(.el-input__inner) {
text-align: left;
}
.code-input-group {
display: flex;
gap: 12px;
}
.code-input {
flex: 1;
}
.password-tip {
display: flex;
align-items: center;
gap: 6px;
margin-top: 6px;
padding: 10px 12px;
background: #f0f9ff;
border: 1px solid #b3d8ff;
border-radius: 4px;
font-size: 12px;
color: #606266;
line-height: 1.5;
}
.password-tip span {
flex: 1;
}
.password-tip .el-icon-info {
color: #667eea;
font-size: 14px;
flex-shrink: 0;
}
.help-link {
margin-top: 8px;
font-size: 12px;
}
.help-link a {
color: #667eea;
text-decoration: none;
cursor: pointer;
}
.help-link a:hover {
text-decoration: underline;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
</style>