批量修复问题
This commit is contained in:
parent
7ad314ff48
commit
1bfa019634
1
components.d.ts
vendored
1
components.d.ts
vendored
@ -22,6 +22,7 @@ declare module 'vue' {
|
|||||||
ElCard: typeof import('element-plus/es')['ElCard']
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
ElCascader: typeof import('element-plus/es')['ElCascader']
|
ElCascader: typeof import('element-plus/es')['ElCascader']
|
||||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||||
|
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
|
|||||||
@ -13,10 +13,6 @@
|
|||||||
<el-switch v-model="otherForm.allowRegister" />
|
<el-switch v-model="otherForm.allowRegister" />
|
||||||
<span class="form-tip">允许用户在前台注册账号</span>
|
<span class="form-tip">允许用户在前台注册账号</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="验证码">
|
|
||||||
<el-switch v-model="otherForm.captchaEnabled" />
|
|
||||||
<span class="form-tip">登录、注册等操作需要验证码</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" @click="saveOtherSettings">保存设置</el-button>
|
<el-button type="primary" @click="saveOtherSettings">保存设置</el-button>
|
||||||
<el-button @click="resetOtherForm">重置</el-button>
|
<el-button @click="resetOtherForm">重置</el-button>
|
||||||
|
|||||||
@ -38,8 +38,18 @@
|
|||||||
<i v-else class="fa-solid fa-eye-slash"></i>
|
<i v-else class="fa-solid fa-eye-slash"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="openVerifyEnabled && verifyType === 'captcha'" class="form-group code-row">
|
||||||
|
<input v-model="captchaInput" type="text" placeholder="请输入4位验证码" class="input code-input" />
|
||||||
|
<button class="code-btn" type="button" @click="generateCaptcha">{{ captchaText }}</button>
|
||||||
|
</div>
|
||||||
|
<div v-if="openVerifyEnabled && (verifyType === 'sms' || verifyType === 'email')" class="form-group code-row">
|
||||||
|
<input v-model="verifyCode" type="text" placeholder="请输入验证码" class="input code-input" />
|
||||||
|
<button class="code-btn" type="button" :disabled="codeSending || codeCountdown > 0" @click="handleSendCode">
|
||||||
|
{{ codeCountdown > 0 ? `${codeCountdown}s` : (codeSending ? "发送中..." : "发送验证码") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<!-- 极验验证码容器 -->
|
<!-- 极验验证码容器 -->
|
||||||
<div style="display: none;" v-if="showCaptchaContainer" class="geetest-container" ref="captchaContainer"></div>
|
<div style="display: none;" v-if="openVerifyEnabled && verifyType === 'geetest' && showCaptchaContainer" class="geetest-container" ref="captchaContainer"></div>
|
||||||
|
|
||||||
<div class="remember-me-row">
|
<div class="remember-me-row">
|
||||||
<label class="remember-me-label">
|
<label class="remember-me-label">
|
||||||
@ -64,11 +74,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, onMounted, nextTick } from "vue";
|
import { ref, onMounted, nextTick } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
import { login, getOpenVerify, getGeetest4Infos } from "@/api/login";
|
import { login, getOpenVerify, getGeetest4Infos, sendLoginCode } from "@/api/login";
|
||||||
|
import { getVerifyInfos } from "@/api/sitesettings.js";
|
||||||
import "@/assets/js/gt4.js";
|
import "@/assets/js/gt4.js";
|
||||||
import { ElMessageBox, ElMessage } from "element-plus";
|
import { ElMessageBox, ElMessage } from "element-plus";
|
||||||
|
|
||||||
@ -82,14 +93,22 @@ const passwordVisible = ref(false);
|
|||||||
const rememberMe = ref(false);
|
const rememberMe = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const errorMsg = ref("");
|
const errorMsg = ref("");
|
||||||
|
const openVerifyEnabled = ref(true);
|
||||||
|
const verifyType = ref("captcha");
|
||||||
|
const captchaInput = ref("");
|
||||||
|
const captchaText = ref("");
|
||||||
|
const verifyCode = ref("");
|
||||||
|
const codeSending = ref(false);
|
||||||
|
const codeCountdown = ref(0);
|
||||||
|
let codeTimer = null;
|
||||||
|
|
||||||
// --- 极验相关变量 ---
|
// --- 极验相关变量 ---
|
||||||
const showCaptchaContainer = ref(false);
|
const showCaptchaContainer = ref(false);
|
||||||
const captchaContainer = ref<HTMLElement | null>(null);
|
const captchaContainer = ref(null);
|
||||||
const captchaInstance = ref<any>(null);
|
const captchaInstance = ref(null);
|
||||||
|
|
||||||
// --- 加载JS脚本 ---
|
// --- 加载JS脚本 ---
|
||||||
const loadScript = (url: string): Promise<void> => {
|
const loadScript = (url) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.src = url;
|
script.src = url;
|
||||||
@ -117,7 +136,8 @@ const cleanCaptchaInstance = () => {
|
|||||||
const performLoginRequest = async () => {
|
const performLoginRequest = async () => {
|
||||||
const res = await login({
|
const res = await login({
|
||||||
account: account.value,
|
account: account.value,
|
||||||
password: password.value
|
password: password.value,
|
||||||
|
code: verifyCode.value
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
@ -149,6 +169,43 @@ const performLoginRequest = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generateCaptcha = () => {
|
||||||
|
// 系统生成4位随机数字验证码
|
||||||
|
captchaText.value = `${Math.floor(1000 + Math.random() * 9000)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const startCodeCountdown = () => {
|
||||||
|
codeCountdown.value = 60;
|
||||||
|
if (codeTimer) clearInterval(codeTimer);
|
||||||
|
codeTimer = setInterval(() => {
|
||||||
|
codeCountdown.value -= 1;
|
||||||
|
if (codeCountdown.value <= 0) {
|
||||||
|
clearInterval(codeTimer);
|
||||||
|
codeTimer = null;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSendCode = async () => {
|
||||||
|
if (codeSending.value || codeCountdown.value > 0) return;
|
||||||
|
if (!account.value.trim()) {
|
||||||
|
errorMsg.value = "请先输入用户名";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
codeSending.value = true;
|
||||||
|
try {
|
||||||
|
const res = await sendLoginCode({ account: account.value, channel: verifyType.value });
|
||||||
|
if (res?.code === 200) {
|
||||||
|
ElMessage.success("验证码已发送");
|
||||||
|
startCodeCountdown();
|
||||||
|
} else {
|
||||||
|
errorMsg.value = res?.msg || "发送验证码失败";
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
codeSending.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// --- 初始化极验4.0 ---
|
// --- 初始化极验4.0 ---
|
||||||
const startGeetest4 = async () => {
|
const startGeetest4 = async () => {
|
||||||
showCaptchaContainer.value = true;
|
showCaptchaContainer.value = true;
|
||||||
@ -186,7 +243,7 @@ const startGeetest4 = async () => {
|
|||||||
product: 'bind',
|
product: 'bind',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
container: captchaContainer.value
|
container: captchaContainer.value
|
||||||
}, (instance: any) => {
|
}, (instance) => {
|
||||||
captchaInstance.value = instance;
|
captchaInstance.value = instance;
|
||||||
|
|
||||||
// 验证成功回调
|
// 验证成功回调
|
||||||
@ -235,7 +292,7 @@ const startGeetest4 = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 错误回调 - 网络错误时跳过验证直接登录
|
// 错误回调 - 网络错误时跳过验证直接登录
|
||||||
instance.onError((err: any) => {
|
instance.onError((err) => {
|
||||||
errorMsg.value = "验证码加载失败,跳过验证直接登录";
|
errorMsg.value = "验证码加载失败,跳过验证直接登录";
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
cleanCaptchaInstance();
|
cleanCaptchaInstance();
|
||||||
@ -265,31 +322,44 @@ const handleLogin = async () => {
|
|||||||
errorMsg.value = "请输入密码";
|
errorMsg.value = "请输入密码";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading.value) return;
|
if (loading.value) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 先判断是否开启验证
|
|
||||||
const verifyRes = await getOpenVerify();
|
const verifyRes = await getOpenVerify();
|
||||||
let openVerify = "0";
|
if (verifyRes?.code === 200 && Array.isArray(verifyRes?.data)) {
|
||||||
|
const openItem = verifyRes.data.find((i) => i.label === "openVerify");
|
||||||
if (verifyRes && verifyRes.code === 200 && Array.isArray(verifyRes.data)) {
|
const typeItem = verifyRes.data.find((i) => i.label === "verifyType");
|
||||||
verifyRes.data.forEach((item: any) => {
|
openVerifyEnabled.value = String(openItem?.value || "0") === "1";
|
||||||
if (item.label === "openVerify") {
|
verifyType.value = typeItem?.value || "captcha";
|
||||||
openVerify = item.value || "0";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果开启了验证,使用极验4.0
|
if (openVerifyEnabled.value && (verifyType.value === "sms" || verifyType.value === "email")) {
|
||||||
if (openVerify === "1") {
|
if (!verifyCode.value.trim()) {
|
||||||
|
errorMsg.value = "请输入验证码";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (openVerifyEnabled.value && verifyType.value === "captcha") {
|
||||||
|
if (!captchaInput.value.trim()) {
|
||||||
|
errorMsg.value = "请输入验证码";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (captchaInput.value.trim() !== captchaText.value) {
|
||||||
|
errorMsg.value = "验证码错误";
|
||||||
|
generateCaptcha();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!openVerifyEnabled.value) {
|
||||||
|
await performLoginRequest();
|
||||||
|
} else if (verifyType.value === "geetest") {
|
||||||
await startGeetest4();
|
await startGeetest4();
|
||||||
} else {
|
} else {
|
||||||
// 直接登录
|
|
||||||
await performLoginRequest();
|
await performLoginRequest();
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err) {
|
||||||
errorMsg.value = err?.response?.data?.msg || err?.message || "登录失败,请重试";
|
errorMsg.value = err?.response?.data?.msg || err?.message || "登录失败,请重试";
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@ -325,6 +395,27 @@ onMounted(() => {
|
|||||||
password.value = localStorage.getItem("loginPassword") || "";
|
password.value = localStorage.getItem("loginPassword") || "";
|
||||||
rememberMe.value = true;
|
rememberMe.value = true;
|
||||||
}
|
}
|
||||||
|
generateCaptcha();
|
||||||
|
getVerifyInfos()
|
||||||
|
.then((res) => {
|
||||||
|
if (res?.code === 200 && res?.data?.use_geetest) {
|
||||||
|
verifyType.value = res.data.use_geetest;
|
||||||
|
openVerifyEnabled.value = Number(res.data.openVerify_enabled ?? 1) === 1;
|
||||||
|
} else {
|
||||||
|
// 兼容旧接口
|
||||||
|
return getOpenVerify().then((verifyRes) => {
|
||||||
|
if (verifyRes?.code === 200 && Array.isArray(verifyRes?.data)) {
|
||||||
|
const openItem = verifyRes.data.find((i) => i.label === "openVerify");
|
||||||
|
if (openItem?.value !== undefined) {
|
||||||
|
openVerifyEnabled.value = String(openItem.value) === "1";
|
||||||
|
}
|
||||||
|
const typeItem = verifyRes.data.find((i) => i.label === "verifyType");
|
||||||
|
if (typeItem?.value) verifyType.value = typeItem.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
const goRegister = () => router.push("/register");
|
const goRegister = () => router.push("/register");
|
||||||
|
|||||||
@ -98,12 +98,11 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="平台端显示" prop="is_platform">
|
<el-form-item label="菜单显示" prop="views">
|
||||||
<el-switch
|
<el-checkbox-group v-model="currentMenu.views" class="views-inline">
|
||||||
v-model="currentMenu.is_platform"
|
<el-checkbox :value="1" label="平台端" />
|
||||||
:active-value="1"
|
<el-checkbox :value="2" label="租户端" />
|
||||||
:inactive-value="0"
|
</el-checkbox-group>
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="权限标识" prop="permission">
|
<el-form-item label="权限标识" prop="permission">
|
||||||
@ -138,7 +137,7 @@ interface Menu {
|
|||||||
sort: number;
|
sort: number;
|
||||||
status: 0 | 1;
|
status: 0 | 1;
|
||||||
is_visible: 0 | 1;
|
is_visible: 0 | 1;
|
||||||
is_platform: 0 | 1;
|
views: number[];
|
||||||
type: 1 | 2 | 3;
|
type: 1 | 2 | 3;
|
||||||
permission: string;
|
permission: string;
|
||||||
children?: Menu[];
|
children?: Menu[];
|
||||||
@ -183,7 +182,7 @@ const currentMenu = ref<Partial<Menu>>({
|
|||||||
sort: 0,
|
sort: 0,
|
||||||
status: 1,
|
status: 1,
|
||||||
is_visible: 1,
|
is_visible: 1,
|
||||||
is_platform: 1,
|
views: [1],
|
||||||
type: 1,
|
type: 1,
|
||||||
permission: '',
|
permission: '',
|
||||||
});
|
});
|
||||||
@ -253,7 +252,7 @@ watch(() => props.menu, (newMenu) => {
|
|||||||
sort: 0,
|
sort: 0,
|
||||||
status: 1,
|
status: 1,
|
||||||
is_visible: 1,
|
is_visible: 1,
|
||||||
is_platform: 1,
|
views: [1],
|
||||||
type: 1,
|
type: 1,
|
||||||
permission: '',
|
permission: '',
|
||||||
};
|
};
|
||||||
@ -291,7 +290,7 @@ watch(() => props.visible, (newVisible) => {
|
|||||||
sort: 0,
|
sort: 0,
|
||||||
status: 1,
|
status: 1,
|
||||||
is_visible: 1,
|
is_visible: 1,
|
||||||
is_platform: 1,
|
views: [1],
|
||||||
type: 1,
|
type: 1,
|
||||||
permission: '',
|
permission: '',
|
||||||
};
|
};
|
||||||
@ -351,6 +350,7 @@ const formRules = ref({
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
sort: [{ required: true, message: "请输入排序号", trigger: "blur" }],
|
sort: [{ required: true, message: "请输入排序号", trigger: "blur" }],
|
||||||
|
views: [{ required: true, message: "请选择菜单显示端", trigger: "change" }],
|
||||||
});
|
});
|
||||||
|
|
||||||
// 级联选择器配置
|
// 级联选择器配置
|
||||||
@ -363,11 +363,23 @@ const cascaderProps = ref({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getMenuSideLabel = (menu: any) => {
|
const getMenuSideLabel = (menu: any) => {
|
||||||
return Number(menu?.is_platform) === 1 ? "平台端" : "租户端";
|
const views = Array.isArray(menu?.views) ? menu.views : [];
|
||||||
|
const hasP = views.includes(1);
|
||||||
|
const hasT = views.includes(2);
|
||||||
|
if (hasP && hasT) return "双端";
|
||||||
|
if (hasP) return "平台端";
|
||||||
|
if (hasT) return "租户端";
|
||||||
|
return "未设置";
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMenuSideTagType = (menu: any) => {
|
const getMenuSideTagType = (menu: any) => {
|
||||||
return Number(menu?.is_platform) === 1 ? "primary" : "warning";
|
const views = Array.isArray(menu?.views) ? menu.views : [];
|
||||||
|
const hasP = views.includes(1);
|
||||||
|
const hasT = views.includes(2);
|
||||||
|
if (hasP && hasT) return "success";
|
||||||
|
if (hasP) return "primary";
|
||||||
|
if (hasT) return "warning";
|
||||||
|
return "info";
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听菜单类型变化,自动清空不相关的字段
|
// 监听菜单类型变化,自动清空不相关的字段
|
||||||
@ -470,4 +482,10 @@ const handleSave = async () => {
|
|||||||
:deep(.el-form-item) {
|
:deep(.el-form-item) {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.views-inline{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -95,10 +95,16 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column prop="is_platform" label="平台端" width="100" align="center">
|
<el-table-column prop="views" label="菜单显示" width="180" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag :type="scope.row.is_platform === 1 ? 'success' : 'info'">
|
<el-tag v-if="Array.isArray(scope.row.views) && scope.row.views.includes(1)" type="primary" style="margin-right:6px;">
|
||||||
{{ scope.row.is_platform === 1 ? "是" : "否" }}
|
平台端
|
||||||
|
</el-tag>
|
||||||
|
<el-tag v-if="Array.isArray(scope.row.views) && scope.row.views.includes(2)" type="warning">
|
||||||
|
租户端
|
||||||
|
</el-tag>
|
||||||
|
<el-tag v-if="!Array.isArray(scope.row.views) || scope.row.views.length === 0" type="info">
|
||||||
|
未设置
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -197,7 +203,7 @@ interface Menu {
|
|||||||
sort: number;
|
sort: number;
|
||||||
status: 0 | 1;
|
status: 0 | 1;
|
||||||
is_visible?: 0 | 1;
|
is_visible?: 0 | 1;
|
||||||
is_platform?: 0 | 1;
|
views?: number[];
|
||||||
type: 1 | 2 | 3; // 1:目录 2:页面 3:接口
|
type: 1 | 2 | 3; // 1:目录 2:页面 3:接口
|
||||||
permission: string;
|
permission: string;
|
||||||
children?: Menu[];
|
children?: Menu[];
|
||||||
|
|||||||
@ -0,0 +1,182 @@
|
|||||||
|
<template>
|
||||||
|
<div class="platform-settings">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="120px"
|
||||||
|
class="settings-form"
|
||||||
|
>
|
||||||
|
<el-card shadow="never" class="settings-card">
|
||||||
|
<el-form-item label="开启验证">
|
||||||
|
<el-switch v-model="formData.openVerify_enabled" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="登录验证方式" prop="use_geetest">
|
||||||
|
<el-radio-group v-model="formData.use_geetest">
|
||||||
|
<el-radio label="captcha">验证码</el-radio>
|
||||||
|
<el-radio label="sms">短信</el-radio>
|
||||||
|
<el-radio label="geetest">极验</el-radio>
|
||||||
|
<el-radio label="email">邮箱</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<template v-if="formData.openVerify_enabled && formData.use_geetest === 'geetest'">
|
||||||
|
<el-form-item label="geetest3ID" prop="geetest3_id">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.geetest3_id"
|
||||||
|
placeholder="请输入 geetest3ID"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="geetest3KEY" prop="geetest3_key">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.geetest3_key"
|
||||||
|
placeholder="请输入 geetest3KEY"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="geetest4ID" prop="geetest4_id">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.geetest4_id"
|
||||||
|
placeholder="请输入 geetest4ID"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="geetest4KEY" prop="geetest4_key">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.geetest4_key"
|
||||||
|
placeholder="请输入 geetest4KEY"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</el-card>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<div class="footer-actions">
|
||||||
|
<el-button @click="handleReset">重置</el-button>
|
||||||
|
<el-button type="primary" :loading="submitting" @click="handleSubmit"
|
||||||
|
>保存设置</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, reactive, ref } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { getVerifyInfos, saveVerifyInfos } from "@/api/sitesettings";
|
||||||
|
|
||||||
|
const STORAGE_KEY = "platform_settings_draft";
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const submitting = ref(false);
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
openVerify_enabled: true,
|
||||||
|
use_geetest: "captcha",
|
||||||
|
geetest3_id: "",
|
||||||
|
geetest3_key: "",
|
||||||
|
geetest4_id: "",
|
||||||
|
geetest4_key: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const validateGeetestField = (_rule, value, callback) => {
|
||||||
|
if (formData.openVerify_enabled && formData.use_geetest === "geetest" && !String(value || "").trim()) {
|
||||||
|
callback(new Error("已启用极验时该参数不能为空"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
geetest3_id: [{ validator: validateGeetestField, trigger: "blur" }],
|
||||||
|
geetest3_key: [{ validator: validateGeetestField, trigger: "blur" }],
|
||||||
|
geetest4_id: [{ validator: validateGeetestField, trigger: "blur" }],
|
||||||
|
geetest4_key: [{ validator: validateGeetestField, trigger: "blur" }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadDraft = () => {
|
||||||
|
const raw = localStorage.getItem(STORAGE_KEY);
|
||||||
|
if (!raw) return;
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(raw);
|
||||||
|
Object.assign(formData, data);
|
||||||
|
} catch {
|
||||||
|
// ignore invalid cache
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadRemoteVerifyInfos = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getVerifyInfos();
|
||||||
|
if (res?.code === 200 && res?.data) {
|
||||||
|
Object.assign(formData, {
|
||||||
|
openVerify_enabled: Number(res.data.openVerify_enabled ?? 1) === 1,
|
||||||
|
use_geetest: res.data.use_geetest || "captcha",
|
||||||
|
geetest3_id: res.data.geetest3_id || "",
|
||||||
|
geetest3_key: res.data.geetest3_key || "",
|
||||||
|
geetest4_id: res.data.geetest4_id || "",
|
||||||
|
geetest4_key: res.data.geetest4_key || "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore remote errors
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveDraft = () => {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(formData));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
Object.assign(formData, {
|
||||||
|
openVerify_enabled: true,
|
||||||
|
use_geetest: "captcha",
|
||||||
|
geetest3_id: "",
|
||||||
|
geetest3_key: "",
|
||||||
|
geetest4_id: "",
|
||||||
|
geetest4_key: "",
|
||||||
|
});
|
||||||
|
saveDraft();
|
||||||
|
ElMessage.success("已重置");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!formRef.value) return;
|
||||||
|
await formRef.value.validate();
|
||||||
|
submitting.value = true;
|
||||||
|
try {
|
||||||
|
await saveVerifyInfos({
|
||||||
|
openVerify_enabled: formData.openVerify_enabled ? 1 : 0,
|
||||||
|
use_geetest: formData.use_geetest,
|
||||||
|
geetest3_id: formData.geetest3_id,
|
||||||
|
geetest3_key: formData.geetest3_key,
|
||||||
|
geetest4_id: formData.geetest4_id,
|
||||||
|
geetest4_key: formData.geetest4_key,
|
||||||
|
});
|
||||||
|
saveDraft();
|
||||||
|
ElMessage.success("保存成功");
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadDraft();
|
||||||
|
loadRemoteVerifyInfos();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.platform-settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-card {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
60
src/views/system/platformsettings/index.vue
Normal file
60
src/views/system/platformsettings/index.vue
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container-box">
|
||||||
|
<div class="header-bar">
|
||||||
|
<h2>平台设置</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-divider></el-divider>
|
||||||
|
|
||||||
|
<div class="settings-container">
|
||||||
|
<el-tabs v-model="activeTab" class="settings-tabs">
|
||||||
|
<el-tab-pane label="验证开关" name="platform">
|
||||||
|
<platformSettings
|
||||||
|
ref="platformSettingsRef"
|
||||||
|
v-if="activeTab === 'platform'"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import platformSettings from "./components/platformSettings.vue";
|
||||||
|
const activeTab = ref("platform");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.container-box {
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tabs {
|
||||||
|
:deep(.el-tabs__header) {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tabs__nav-wrap::after) {
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue
Block a user