|
|
@@ -0,0 +1,735 @@
|
|
|
+<template>
|
|
|
+ <div class="security-container">
|
|
|
+ <div class="security-header">
|
|
|
+ <h2 class="page-title">
|
|
|
+ <el-icon :size="24"><Lock /></el-icon>
|
|
|
+ 安全中心
|
|
|
+ </h2>
|
|
|
+ <p class="page-desc">管理您的账号安全设置,保护账户信息</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="security-grid">
|
|
|
+ <!-- 支付密码卡片 -->
|
|
|
+ <el-card class="security-card" shadow="hover">
|
|
|
+ <div class="card-icon payment-icon">
|
|
|
+ <el-icon :size="40"><Lock /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
+ <h3 class="card-title">支付密码</h3>
|
|
|
+ <p class="card-desc">{{ isPaymentPwdSet ? '已设置支付密码' : '未设置支付密码,建议设置以提高账号安全性' }}</p>
|
|
|
+ <div class="card-status">
|
|
|
+ <el-tag :type="isPaymentPwdSet ? 'success' : 'info'" size="small">
|
|
|
+ {{ isPaymentPwdSet ? '已开启' : '未开启' }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="card-action">
|
|
|
+ <el-button v-if="!isPaymentPwdSet" type="primary" @click="handleSetPaymentPwd">立即设置</el-button>
|
|
|
+ <el-button v-else type="primary" plain @click="handleSetPaymentPwd">修改密码</el-button>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 白名单卡片 -->
|
|
|
+ <!-- <el-card class="security-card" shadow="hover">
|
|
|
+ <div class="card-icon whitelist-icon">
|
|
|
+ <el-icon :size="40"><List /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
+ <h3 class="card-title">白名单设置</h3>
|
|
|
+ <p class="card-desc">设置允许访问的IP地址白名单</p>
|
|
|
+ </div>
|
|
|
+ <div class="card-action">
|
|
|
+ <el-button type="primary" @click="handleSetWhitelist">设置白名单</el-button>
|
|
|
+ </div>
|
|
|
+ </el-card> -->
|
|
|
+
|
|
|
+ <!-- Google验证器卡片 -->
|
|
|
+ <el-card class="security-card" shadow="hover">
|
|
|
+ <div class="card-icon google-icon">
|
|
|
+ <svg class="google-svg" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
|
+ <path
|
|
|
+ d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
|
|
+ fill="#4285F4"
|
|
|
+ />
|
|
|
+ <path
|
|
|
+ d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
|
|
+ fill="#34A853"
|
|
|
+ />
|
|
|
+ <path
|
|
|
+ d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
|
|
+ fill="#FBBC05"
|
|
|
+ />
|
|
|
+ <path
|
|
|
+ d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
|
|
+ fill="#EA4335"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
+ <h3 class="card-title">Google验证器</h3>
|
|
|
+ <p class="card-desc">{{ isGoogleBound ? '已绑定Google验证器' : '未绑定Google验证器,建议开启以提高账号安全性' }}</p>
|
|
|
+ <div class="card-status">
|
|
|
+ <el-tag :type="isGoogleBound ? 'success' : 'info'" size="small">
|
|
|
+ {{ isGoogleBound ? '已开启' : '未开启' }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="card-action">
|
|
|
+ <el-button v-if="!isGoogleBound" type="primary" @click="handleBindGoogle">立即绑定</el-button>
|
|
|
+ <el-button v-else type="danger" plain @click="handleUnbindGoogle">解除绑定</el-button>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Google验证器绑定对话框 -->
|
|
|
+ <el-dialog v-model="googleDialogVisible" title="绑定Google验证器" width="500px" :close-on-click-modal="false">
|
|
|
+ <div class="google-bind-content">
|
|
|
+ <el-steps :active="googleBindStep" finish-status="success" align-center>
|
|
|
+ <el-step title="扫描二维码" />
|
|
|
+ <el-step title="输入验证码" />
|
|
|
+ <el-step title="完成" />
|
|
|
+ </el-steps>
|
|
|
+
|
|
|
+ <div v-if="googleBindStep === 0" class="step-content">
|
|
|
+ <div class="qrcode-container">
|
|
|
+ <div v-show="googleAuth.loading" class="loading-box">
|
|
|
+ <el-icon class="is-loading" :size="40"><Loading /></el-icon>
|
|
|
+ <p>正在生成二维码...</p>
|
|
|
+ </div>
|
|
|
+ <div v-show="!googleAuth.loading && googleAuth.qrCode" class="qrcode-box">
|
|
|
+ <canvas ref="qrcodeCanvas"></canvas>
|
|
|
+ <p class="qrcode-tip">请使用Google Authenticator扫描二维码</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="step-actions">
|
|
|
+ <el-button @click="googleDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="googleBindStep = 1" :disabled="!googleAuth.qrCode">下一步</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="googleBindStep === 1" class="step-content">
|
|
|
+ <el-form ref="googleFormRef" :model="googleForm" :rules="googleRules" label-width="100px">
|
|
|
+ <el-form-item label="验证码" prop="code">
|
|
|
+ <el-input v-model="googleForm.code" placeholder="请输入6位验证码" maxlength="6" show-word-limit clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-alert title="请打开Google Authenticator应用,输入显示的6位数字验证码" type="info" :closable="false" />
|
|
|
+ </el-form>
|
|
|
+ <div class="step-actions">
|
|
|
+ <el-button @click="googleBindStep = 0">上一步</el-button>
|
|
|
+ <el-button type="primary" @click="submitGoogleBind" :loading="googleAuth.binding">确认绑定</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="googleBindStep === 2" class="step-content">
|
|
|
+ <el-result icon="success" title="绑定成功" sub-title="您已成功绑定Google验证器">
|
|
|
+ <template #extra>
|
|
|
+ <el-button type="primary" @click="closeGoogleDialog">完成</el-button>
|
|
|
+ </template>
|
|
|
+ </el-result>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- Google验证器解绑对话框 -->
|
|
|
+ <el-dialog v-model="unbindDialogVisible" title="解除绑定Google验证器" width="450px" :close-on-click-modal="false">
|
|
|
+ <el-form ref="unbindFormRef" :model="unbindForm" :rules="unbindRules" label-width="80px">
|
|
|
+ <el-alert title="解除绑定后,您将无法使用Google验证器进行二次验证" type="warning" :closable="false" style="margin-bottom: 20px" />
|
|
|
+ <el-form-item label="登录密码" prop="pwd">
|
|
|
+ <el-input v-model="unbindForm.pwd" type="password" placeholder="请输入登录密码" show-password clearable />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="unbindDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="danger" @click="submitGoogleUnbind" :loading="googleAuth.unbinding">确认解绑</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 支付密码设置对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="paymentPwdDialogVisible"
|
|
|
+ :title="isPaymentPwdSet ? '修改支付密码' : '设置支付密码'"
|
|
|
+ width="450px"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ >
|
|
|
+ <el-form ref="paymentPwdFormRef" :model="paymentPwdForm" :rules="paymentPwdRules" label-width="100px">
|
|
|
+ <el-alert v-if="!isPaymentPwdSet" title="首次设置支付密码,无需输入旧密码" type="info" :closable="false" style="margin-bottom: 20px" />
|
|
|
+ <el-form-item v-if="isPaymentPwdSet" label="旧密码" prop="oldPassword">
|
|
|
+ <el-input v-model="paymentPwdForm.oldPassword" type="password" placeholder="请输入旧支付密码" show-password clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="新密码" prop="newPassword">
|
|
|
+ <el-input v-model="paymentPwdForm.newPassword" type="password" placeholder="请输入新支付密码" show-password clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="确认密码" prop="confirmPassword">
|
|
|
+ <el-input v-model="paymentPwdForm.confirmPassword" type="password" placeholder="请再次输入新支付密码" show-password clearable />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="paymentPwdDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitPaymentPwd" :loading="paymentPwdSubmitting">确认</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 白名单设置对话框 -->
|
|
|
+ <el-dialog v-model="whitelistDialogVisible" title="白名单设置" width="550px" :close-on-click-modal="false">
|
|
|
+ <el-form ref="whitelistFormRef" :model="whitelistForm" :rules="whitelistRules" label-width="80px">
|
|
|
+ <el-alert title="请输入允许访问的IP地址,多个IP用英文逗号分隔" type="info" :closable="false" style="margin-bottom: 20px" />
|
|
|
+ <el-form-item label="IP地址" prop="whitelist">
|
|
|
+ <el-input v-model="whitelistForm.whitelist" type="textarea" :rows="6" placeholder="例如: 192.168.1.1,192.168.1.2" clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-text type="info" size="small">提示: 留空表示不限制IP访问</el-text>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="whitelistDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitWhitelist" :loading="whitelistSubmitting">确认</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts" name="settingsSecurity">
|
|
|
+import { ref, reactive, computed, nextTick } from 'vue';
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
+import { Lock, Loading, List } from '@element-plus/icons-vue';
|
|
|
+import { getGoogleCode, bindingGoogle, unbindingGoogle, getWhitelist, editWhitelist, editSecondaryPwd } from '/@/api/admin/setting';
|
|
|
+import { useUserInfo } from '/@/stores/userInfo';
|
|
|
+import { Session } from '/@/utils/storage';
|
|
|
+import QRCode from 'qrcode';
|
|
|
+
|
|
|
+// 获取用户信息store
|
|
|
+const userStore = useUserInfo();
|
|
|
+
|
|
|
+// 二维码canvas引用
|
|
|
+const qrcodeCanvas = ref<HTMLCanvasElement>();
|
|
|
+
|
|
|
+// 从全局用户信息获取绑定状态
|
|
|
+const isGoogleBound = computed(() => userStore.userInfos?.user?.googleSecretFlag || false);
|
|
|
+
|
|
|
+// 从全局用户信息获取支付密码设置状态
|
|
|
+const isPaymentPwdSet = computed(() => userStore.userInfos?.user?.secondaryPwdFlag || false);
|
|
|
+
|
|
|
+// 白名单数据
|
|
|
+const whitelistData = ref('');
|
|
|
+
|
|
|
+const googleAuth = reactive({
|
|
|
+ qrCode: '', // 二维码数据
|
|
|
+ loading: false, // 加载中
|
|
|
+ binding: false, // 绑定中
|
|
|
+ unbinding: false, // 解绑中
|
|
|
+});
|
|
|
+
|
|
|
+// Google绑定对话框
|
|
|
+const googleDialogVisible = ref(false);
|
|
|
+const googleBindStep = ref(0); // 0:扫描二维码 1:输入验证码 2:完成
|
|
|
+const googleFormRef = ref();
|
|
|
+const googleForm = reactive({
|
|
|
+ code: '',
|
|
|
+});
|
|
|
+
|
|
|
+// Google绑定表单验证
|
|
|
+const googleRules = {
|
|
|
+ code: [
|
|
|
+ { required: true, message: '请输入验证码', trigger: 'blur' },
|
|
|
+ { len: 6, message: '验证码必须为6位数字', trigger: 'blur' },
|
|
|
+ { pattern: /^\d{6}$/, message: '验证码必须为6位数字', trigger: 'blur' },
|
|
|
+ ],
|
|
|
+};
|
|
|
+
|
|
|
+// Google解绑对话框
|
|
|
+const unbindDialogVisible = ref(false);
|
|
|
+const unbindFormRef = ref();
|
|
|
+const unbindForm = reactive({
|
|
|
+ pwd: '',
|
|
|
+});
|
|
|
+
|
|
|
+// Google解绑表单验证
|
|
|
+const unbindRules = {
|
|
|
+ pwd: [{ required: true, message: '请输入登录密码', trigger: 'blur' }],
|
|
|
+};
|
|
|
+
|
|
|
+// 支付密码设置对话框
|
|
|
+const paymentPwdDialogVisible = ref(false);
|
|
|
+const paymentPwdFormRef = ref();
|
|
|
+const paymentPwdSubmitting = ref(false);
|
|
|
+const paymentPwdForm = reactive({
|
|
|
+ oldPassword: '',
|
|
|
+ newPassword: '',
|
|
|
+ confirmPassword: '',
|
|
|
+});
|
|
|
+
|
|
|
+// 支付密码表单验证
|
|
|
+const validateConfirmPassword = (rule: any, value: any, callback: any) => {
|
|
|
+ if (value === '') {
|
|
|
+ callback(new Error('请再次输入新支付密码'));
|
|
|
+ } else if (value !== paymentPwdForm.newPassword) {
|
|
|
+ callback(new Error('两次输入的密码不一致'));
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const paymentPwdRules = computed(() => {
|
|
|
+ const rules: any = {
|
|
|
+ newPassword: [
|
|
|
+ { required: true, message: '请输入新支付密码', trigger: 'blur' },
|
|
|
+ { min: 6, message: '密码长度不能少于6位', trigger: 'blur' },
|
|
|
+ ],
|
|
|
+ confirmPassword: [{ validator: validateConfirmPassword, trigger: 'blur' }],
|
|
|
+ };
|
|
|
+ if (isPaymentPwdSet.value) {
|
|
|
+ rules.oldPassword = [{ required: true, message: '请输入旧支付密码', trigger: 'blur' }];
|
|
|
+ }
|
|
|
+ return rules;
|
|
|
+});
|
|
|
+
|
|
|
+// 白名单设置对话框
|
|
|
+const whitelistDialogVisible = ref(false);
|
|
|
+const whitelistFormRef = ref();
|
|
|
+const whitelistSubmitting = ref(false);
|
|
|
+const whitelistForm = reactive({
|
|
|
+ whitelist: '',
|
|
|
+});
|
|
|
+
|
|
|
+// 白名单表单验证
|
|
|
+const whitelistRules = {
|
|
|
+ whitelist: [
|
|
|
+ {
|
|
|
+ pattern: /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(,\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})*$/,
|
|
|
+ message: '请输入正确的IP地址格式',
|
|
|
+ trigger: 'blur',
|
|
|
+ required: false,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+};
|
|
|
+
|
|
|
+// 获取Google验证器二维码
|
|
|
+const getGoogleQrCode = async () => {
|
|
|
+ googleAuth.loading = true;
|
|
|
+ try {
|
|
|
+ const res = await getGoogleCode();
|
|
|
+ if (res.code === 0) {
|
|
|
+ googleAuth.qrCode = res.data; // 保存二维码数据字符串
|
|
|
+ googleAuth.loading = false; // 先关闭 loading
|
|
|
+ // 等待DOM更新后生成二维码
|
|
|
+ await nextTick();
|
|
|
+ if (qrcodeCanvas.value) {
|
|
|
+ try {
|
|
|
+ await QRCode.toCanvas(qrcodeCanvas.value, res.data, {
|
|
|
+ width: 250,
|
|
|
+ margin: 2,
|
|
|
+ color: {
|
|
|
+ dark: '#000000',
|
|
|
+ light: '#ffffff',
|
|
|
+ },
|
|
|
+ errorCorrectionLevel: 'M',
|
|
|
+ });
|
|
|
+ } catch (err) {
|
|
|
+ ElMessage.error('二维码生成失败');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ ElMessage.error('二维码生成失败');
|
|
|
+ }
|
|
|
+ return; // 提前返回,不执行 finally
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '获取二维码失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('获取二维码失败');
|
|
|
+ } finally {
|
|
|
+ googleAuth.loading = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 绑定Google验证器
|
|
|
+const handleBindGoogle = () => {
|
|
|
+ googleDialogVisible.value = true;
|
|
|
+ googleBindStep.value = 0;
|
|
|
+ googleForm.code = '';
|
|
|
+ googleAuth.qrCode = '';
|
|
|
+ getGoogleQrCode();
|
|
|
+};
|
|
|
+
|
|
|
+// 提交Google绑定
|
|
|
+const submitGoogleBind = async () => {
|
|
|
+ if (!googleFormRef.value) return;
|
|
|
+ await googleFormRef.value.validate(async (valid: boolean) => {
|
|
|
+ if (valid) {
|
|
|
+ googleAuth.binding = true;
|
|
|
+ try {
|
|
|
+ const res = await bindingGoogle(googleForm.code);
|
|
|
+ if (res.code === 0) {
|
|
|
+ googleBindStep.value = 2;
|
|
|
+ // 使用确认框提示用户
|
|
|
+ await ElMessageBox.alert('绑定成功,点击确定后将自动注销并跳转到登录页', '绑定成功', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ type: 'success',
|
|
|
+ showClose: false,
|
|
|
+ callback: () => {
|
|
|
+ Session.clear();
|
|
|
+ window.location.href = '/login';
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ // 全局已经显示错误消息,这里只需要捕获异常
|
|
|
+ } finally {
|
|
|
+ googleAuth.binding = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 关闭Google绑定对话框
|
|
|
+const closeGoogleDialog = () => {
|
|
|
+ googleDialogVisible.value = false;
|
|
|
+ googleBindStep.value = 0;
|
|
|
+ googleForm.code = '';
|
|
|
+};
|
|
|
+
|
|
|
+// 解绑Google验证器
|
|
|
+const handleUnbindGoogle = () => {
|
|
|
+ unbindDialogVisible.value = true;
|
|
|
+ unbindForm.pwd = '';
|
|
|
+};
|
|
|
+
|
|
|
+// 提交Google解绑
|
|
|
+const submitGoogleUnbind = async () => {
|
|
|
+ if (!unbindFormRef.value) return;
|
|
|
+ await unbindFormRef.value.validate(async (valid: boolean) => {
|
|
|
+ if (valid) {
|
|
|
+ googleAuth.unbinding = true;
|
|
|
+ try {
|
|
|
+ const res = await unbindingGoogle(unbindForm.pwd);
|
|
|
+ if (res.code === 0) {
|
|
|
+ unbindDialogVisible.value = false;
|
|
|
+ // 使用确认框提示用户
|
|
|
+ await ElMessageBox.alert('解绑成功,点击确定后将自动注销并跳转到登录页', '解绑成功', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ type: 'success',
|
|
|
+ showClose: false,
|
|
|
+ callback: () => {
|
|
|
+ Session.clear();
|
|
|
+ window.location.href = '/login';
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ // 全局已经显示错误消息,这里只需要捕获异常
|
|
|
+ } finally {
|
|
|
+ googleAuth.unbinding = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 打开支付密码设置对话框
|
|
|
+const handleSetPaymentPwd = () => {
|
|
|
+ paymentPwdDialogVisible.value = true;
|
|
|
+ paymentPwdForm.oldPassword = '';
|
|
|
+ paymentPwdForm.newPassword = '';
|
|
|
+ paymentPwdForm.confirmPassword = '';
|
|
|
+};
|
|
|
+
|
|
|
+// 提交支付密码设置
|
|
|
+const submitPaymentPwd = async () => {
|
|
|
+ if (!paymentPwdFormRef.value) return;
|
|
|
+ await paymentPwdFormRef.value.validate(async (valid: boolean) => {
|
|
|
+ if (valid) {
|
|
|
+ paymentPwdSubmitting.value = true;
|
|
|
+ try {
|
|
|
+ const res = await editSecondaryPwd(paymentPwdForm.newPassword, isPaymentPwdSet.value ? paymentPwdForm.oldPassword : undefined);
|
|
|
+ if (res.code === 0) {
|
|
|
+ paymentPwdDialogVisible.value = false;
|
|
|
+ // 使用确认框提示用户
|
|
|
+ await ElMessageBox.alert('设置成功,点击确定后将自动注销并跳转到登录页', '设置成功', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ type: 'success',
|
|
|
+ showClose: false,
|
|
|
+ callback: () => {
|
|
|
+ Session.clear();
|
|
|
+ window.location.href = '/login';
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ // 全局已经显示错误消息
|
|
|
+ } finally {
|
|
|
+ paymentPwdSubmitting.value = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 打开白名单设置对话框
|
|
|
+const handleSetWhitelist = async () => {
|
|
|
+ whitelistDialogVisible.value = true;
|
|
|
+ // 加载当前白名单
|
|
|
+ try {
|
|
|
+ const res = await getWhitelist();
|
|
|
+ if (res.code === 0) {
|
|
|
+ whitelistForm.whitelist = res.data || '';
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ // 加载失败不影响打开对话框
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 提交白名单设置
|
|
|
+const submitWhitelist = async () => {
|
|
|
+ if (!whitelistFormRef.value) return;
|
|
|
+ await whitelistFormRef.value.validate(async (valid: boolean) => {
|
|
|
+ if (valid) {
|
|
|
+ whitelistSubmitting.value = true;
|
|
|
+ try {
|
|
|
+ const res = await editWhitelist(whitelistForm.whitelist.trim());
|
|
|
+ if (res.code === 0) {
|
|
|
+ ElMessage.success('设置成功');
|
|
|
+ whitelistDialogVisible.value = false;
|
|
|
+ whitelistData.value = whitelistForm.whitelist.trim();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ // 全局已经显示错误消息
|
|
|
+ } finally {
|
|
|
+ whitelistSubmitting.value = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 初始化加载白名单数据
|
|
|
+const loadWhitelist = async () => {
|
|
|
+ try {
|
|
|
+ const res = await getWhitelist();
|
|
|
+ if (res.code === 0) {
|
|
|
+ whitelistData.value = res.data || '';
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ // 加载失败不影响页面显示
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 页面加载时获取白名单数据
|
|
|
+loadWhitelist();
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.security-container {
|
|
|
+ padding: 24px;
|
|
|
+ min-height: calc(100vh - 100px);
|
|
|
+
|
|
|
+ .security-header {
|
|
|
+ margin-bottom: 32px;
|
|
|
+
|
|
|
+ .page-title {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+ margin: 0 0 8px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-desc {
|
|
|
+ font-size: 14px;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .security-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
|
|
+ gap: 24px;
|
|
|
+ max-width: 1400px;
|
|
|
+
|
|
|
+ .security-card {
|
|
|
+ position: relative;
|
|
|
+ padding: 28px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ border-radius: 12px;
|
|
|
+ cursor: default;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ transform: translateY(-4px);
|
|
|
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-icon {
|
|
|
+ width: 64px;
|
|
|
+ height: 64px;
|
|
|
+ border-radius: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 20px;
|
|
|
+
|
|
|
+ &.payment-icon {
|
|
|
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.whitelist-icon {
|
|
|
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.google-icon {
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ padding: 12px;
|
|
|
+
|
|
|
+ .google-svg {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-content {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 80px;
|
|
|
+
|
|
|
+ .card-title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+ margin: 0 0 8px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-desc {
|
|
|
+ font-size: 13px;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ line-height: 1.6;
|
|
|
+ margin: 0 0 12px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-status {
|
|
|
+ margin-top: 8px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-action {
|
|
|
+ margin-top: 20px;
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+
|
|
|
+ .el-button {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Google绑定对话框样式
|
|
|
+ .google-bind-content {
|
|
|
+ padding: 20px 0;
|
|
|
+
|
|
|
+ .el-steps {
|
|
|
+ margin-bottom: 40px;
|
|
|
+
|
|
|
+ // 自定义步骤条样式
|
|
|
+ :deep(.el-step__head) {
|
|
|
+ .el-step__icon {
|
|
|
+ width: 30px;
|
|
|
+ height: 30px;
|
|
|
+
|
|
|
+ .el-step__icon-inner {
|
|
|
+ font-size: 18px !important;
|
|
|
+ font-weight: 600 !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-step__title) {
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .step-content {
|
|
|
+ min-height: 300px;
|
|
|
+
|
|
|
+ .qrcode-container {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ min-height: 280px;
|
|
|
+
|
|
|
+ .loading-box {
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ p {
|
|
|
+ margin-top: 16px;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .qrcode-box {
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ canvas {
|
|
|
+ border: 1px solid var(--el-border-color);
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 12px;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .qrcode-tip {
|
|
|
+ margin-top: 16px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .step-actions {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 12px;
|
|
|
+ margin-top: 32px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-result {
|
|
|
+ padding: 40px 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 响应式布局
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .security-container {
|
|
|
+ .security-grid {
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .security-container {
|
|
|
+ padding: 16px;
|
|
|
+
|
|
|
+ .security-header {
|
|
|
+ margin-bottom: 24px;
|
|
|
+
|
|
|
+ .page-title {
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .security-grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ gap: 16px;
|
|
|
+
|
|
|
+ .security-card {
|
|
|
+ padding: 20px;
|
|
|
+
|
|
|
+ .card-icon {
|
|
|
+ width: 56px;
|
|
|
+ height: 56px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|