| | |
| | | flex: 0 0 118px; |
| | | } |
| | | |
| | | .mfa-panel { |
| | | border: 1px solid rgba(222, 230, 239, 0.92); |
| | | border-radius: 14px; |
| | | background: #f8fbff; |
| | | padding: 14px 16px; |
| | | } |
| | | |
| | | .mfa-head { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 12px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .mfa-title { |
| | | font-size: 14px; |
| | | font-weight: 700; |
| | | color: #2f4358; |
| | | } |
| | | |
| | | .mfa-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .mfa-meta { |
| | | display: grid; |
| | | grid-template-columns: repeat(3, minmax(0, 1fr)); |
| | | gap: 10px; |
| | | } |
| | | |
| | | .mfa-meta-item { |
| | | padding: 10px 12px; |
| | | border-radius: 12px; |
| | | background: rgba(255, 255, 255, 0.95); |
| | | border: 1px solid rgba(226, 233, 242, 0.96); |
| | | } |
| | | |
| | | .mfa-meta-label { |
| | | font-size: 12px; |
| | | color: #7b8b9b; |
| | | margin-bottom: 6px; |
| | | } |
| | | |
| | | .mfa-meta-value { |
| | | font-size: 13px; |
| | | color: #2f4358; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .mfa-tip { |
| | | margin-top: 10px; |
| | | font-size: 12px; |
| | | line-height: 1.7; |
| | | color: #7b8b9b; |
| | | } |
| | | |
| | | .footer-bar { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .password-dialog .el-dialog__header { |
| | | .password-dialog .el-dialog__header, |
| | | .mfa-dialog .el-dialog__header { |
| | | padding: 18px 20px 12px; |
| | | border-bottom: 1px solid rgba(222, 230, 239, 0.92); |
| | | background: #f8fbff; |
| | | } |
| | | |
| | | .password-dialog .el-dialog__title { |
| | | .password-dialog .el-dialog__title, |
| | | .mfa-dialog .el-dialog__title { |
| | | font-weight: 700; |
| | | color: var(--text-main); |
| | | } |
| | | |
| | | .password-dialog .el-dialog__body { |
| | | .password-dialog .el-dialog__body, |
| | | .mfa-dialog .el-dialog__body { |
| | | padding: 18px 20px 12px; |
| | | } |
| | | |
| | |
| | | padding-top: 4px; |
| | | } |
| | | |
| | | .mfa-setup { |
| | | margin-bottom: 16px; |
| | | padding: 14px; |
| | | border-radius: 14px; |
| | | background: rgba(248, 251, 255, 0.92); |
| | | border: 1px solid rgba(226, 233, 242, 0.96); |
| | | } |
| | | |
| | | .mfa-setup-tip { |
| | | font-size: 12px; |
| | | line-height: 1.7; |
| | | color: #738396; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .mfa-qr-wrap { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | min-height: 220px; |
| | | margin-bottom: 14px; |
| | | background: #fff; |
| | | border-radius: 14px; |
| | | border: 1px dashed rgba(210, 220, 233, 0.96); |
| | | } |
| | | |
| | | .mfa-qr-wrap img { |
| | | width: 220px; |
| | | height: 220px; |
| | | object-fit: contain; |
| | | } |
| | | |
| | | .mfa-secret-row { |
| | | display: flex; |
| | | gap: 10px; |
| | | align-items: center; |
| | | } |
| | | |
| | | .mfa-secret-row .el-input { |
| | | flex: 1; |
| | | } |
| | | |
| | | .mfa-footer { |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 12px; |
| | | padding-top: 6px; |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .page-shell { |
| | | padding: 10px; |
| | | } |
| | | |
| | | .password-row { |
| | | .password-row, |
| | | .mfa-secret-row { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .form-shell { |
| | | max-width: none; |
| | | } |
| | | |
| | | .mfa-head { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .mfa-meta { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <div class="card-body"> |
| | | <div class="form-shell"> |
| | | <input id="id" type="hidden" v-model="form.id"> |
| | | <input id="password" type="hidden" v-model="form.password"> |
| | | <el-form |
| | | ref="profileForm" |
| | | class="profile-form" |
| | |
| | | <div class="password-row"> |
| | | <el-input class="password-mask" value="已设置密码" disabled></el-input> |
| | | <el-button type="primary" plain icon="el-icon-lock" @click="openPasswordDialog">修改密码</el-button> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :xs="24"> |
| | | <el-form-item label="MFA"> |
| | | <div class="mfa-panel"> |
| | | <div class="mfa-head"> |
| | | <div class="mfa-title">多因子登录验证</div> |
| | | <div class="mfa-actions"> |
| | | <el-button |
| | | v-if="Number(form.mfaAllow) === 1 && Number(form.mfaEnabled) !== 1" |
| | | type="primary" |
| | | plain |
| | | icon="el-icon-key" |
| | | @click="openMfaEnableDialog">启用 MFA</el-button> |
| | | <el-button |
| | | v-if="Number(form.mfaEnabled) === 1" |
| | | type="danger" |
| | | plain |
| | | icon="el-icon-switch-button" |
| | | @click="openMfaDisableDialog">停用 MFA</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="mfa-meta"> |
| | | <div class="mfa-meta-item"> |
| | | <div class="mfa-meta-label">账号授权</div> |
| | | <div class="mfa-meta-value">{{ form.mfaAllow$ || '--' }}</div> |
| | | </div> |
| | | <div class="mfa-meta-item"> |
| | | <div class="mfa-meta-label">启用状态</div> |
| | | <div class="mfa-meta-value">{{ form.mfaEnabled$ || '--' }}</div> |
| | | </div> |
| | | <div class="mfa-meta-item"> |
| | | <div class="mfa-meta-label">绑定时间</div> |
| | | <div class="mfa-meta-value">{{ form.mfaBoundTime$ || '--' }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="mfa-tip"> |
| | | <span v-if="Number(form.mfaAllow) !== 1">当前账号未开通 MFA 使用权限,请联系管理员授权。</span> |
| | | <span v-else-if="Number(form.mfaEnabled) === 1">已绑定密钥:{{ form.mfaMaskedSecret || '--' }}。登录时需要再输入一次动态验证码。</span> |
| | | <span v-else>当前账号已允许使用 MFA,启用后登录会增加一次 6 位动态验证码校验。</span> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | </div> |
| | | </el-form> |
| | | </el-dialog> |
| | | |
| | | <el-dialog |
| | | class="mfa-dialog" |
| | | :title="mfaDialogMode === 'enable' ? '启用 MFA' : '停用 MFA'" |
| | | :visible.sync="mfaDialogVisible" |
| | | width="520px" |
| | | :close-on-click-modal="false" |
| | | @close="closeMfaDialog" |
| | | append-to-body> |
| | | <div v-if="mfaDialogMode === 'enable'" class="mfa-setup"> |
| | | <div class="mfa-setup-tip">请先使用 Google Authenticator、Microsoft Authenticator 等身份验证器扫描二维码,再输入当前密码和 6 位动态码完成绑定。</div> |
| | | <div class="mfa-qr-wrap" v-loading="mfaSetupLoading"> |
| | | <img v-if="mfaSetup.qrCode" :src="mfaSetup.qrCode" alt="MFA QR Code"> |
| | | <span v-else>二维码生成中...</span> |
| | | </div> |
| | | <div class="mfa-secret-row"> |
| | | <el-input :value="mfaSetup.secret" readonly placeholder="MFA密钥"></el-input> |
| | | <el-button plain @click="copySecret">复制密钥</el-button> |
| | | </div> |
| | | </div> |
| | | <div v-else class="mfa-setup"> |
| | | <div class="mfa-setup-tip">停用 MFA 前,请输入当前密码和身份验证器中的 6 位动态码进行确认。</div> |
| | | </div> |
| | | <el-form |
| | | ref="mfaForm" |
| | | class="password-form" |
| | | :model="mfaForm" |
| | | :rules="mfaRules" |
| | | label-width="112px" |
| | | size="small" |
| | | @submit.native.prevent> |
| | | <el-form-item label="当前密码" prop="currentPassword"> |
| | | <el-input v-model="mfaForm.currentPassword" type="password" show-password autocomplete="off"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="动态验证码" prop="code"> |
| | | <el-input v-model.trim="mfaForm.code" maxlength="6" autocomplete="off" placeholder="请输入6位验证码"></el-input> |
| | | </el-form-item> |
| | | <div class="mfa-footer"> |
| | | <el-button @click="closeMfaDialog">关闭</el-button> |
| | | <el-button type="primary" :loading="mfaSubmitting" @click="handleMfaSubmit">保存</el-button> |
| | | </div> |
| | | </el-form> |
| | | </el-dialog> |
| | | </div> |
| | | </body> |
| | | <script type="text/javascript" src="../static/js/jquery/jquery-3.3.1.min.js"></script> |
| | |
| | | <script type="text/javascript" src="../static/js/common.js"></script> |
| | | <script type="text/javascript" src="../static/vue/js/vue.min.js"></script> |
| | | <script type="text/javascript" src="../static/vue/element/element.js"></script> |
| | | <script type="text/javascript" src="../static/js/detail/detail.js?v=20260310_detail_vue3"></script> |
| | | <script type="text/javascript" src="../static/js/detail/detail.js?v=20260311_detail_mfa"></script> |
| | | </html> |