commits
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,182 +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>
|
||||
<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>
|
||||
|
||||
@@ -1,288 +1,288 @@
|
||||
<template>
|
||||
<div class="storage-settings">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="140px"
|
||||
class="settings-form"
|
||||
>
|
||||
<el-card shadow="never" class="settings-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>存储位置配置</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form-item label="存储类型" prop="storage_type">
|
||||
<el-radio-group v-model="formData.storage_type">
|
||||
<el-radio label="local">本地存储</el-radio>
|
||||
<el-radio label="qiniu">七牛云存储</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="formData.storage_type === 'qiniu'">
|
||||
<el-divider content-position="left">七牛云配置</el-divider>
|
||||
|
||||
<el-form-item label="AccessKey" prop="qiniu_access_key">
|
||||
<el-input
|
||||
v-model="formData.qiniu_access_key"
|
||||
placeholder="请输入七牛云 AccessKey"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="SecretKey" prop="qiniu_secret_key">
|
||||
<el-input
|
||||
v-model="formData.qiniu_secret_key"
|
||||
type="password"
|
||||
placeholder="请输入七牛云 SecretKey"
|
||||
show-password
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Bucket" prop="qiniu_bucket">
|
||||
<el-input
|
||||
v-model="formData.qiniu_bucket"
|
||||
placeholder="请输入存储空间名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="CDN域名" prop="qiniu_domain">
|
||||
<el-input
|
||||
v-model="formData.qiniu_domain"
|
||||
placeholder="请输入CDN加速域名,如:https://cdn.example.com"
|
||||
clearable
|
||||
>
|
||||
<template #prepend>
|
||||
<el-icon><Link /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="form-tip">用于访问上传的文件,需要在七牛云控制台配置</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="存储区域" prop="qiniu_region">
|
||||
<el-select
|
||||
v-model="formData.qiniu_region"
|
||||
placeholder="请选择存储区域"
|
||||
clearable
|
||||
>
|
||||
<el-option label="华东-浙江" value="z0" />
|
||||
<el-option label="华北-河北" value="z1" />
|
||||
<el-option label="华南-广东" value="z2" />
|
||||
<el-option label="北美-洛杉矶" value="na0" />
|
||||
<el-option label="亚太-新加坡" value="as0" />
|
||||
<el-option label="华东-浙江2" value="cn-east-2" />
|
||||
</el-select>
|
||||
<div class="form-tip">请根据您的Bucket所在区域选择</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-alert
|
||||
v-if="formData.storage_type === 'local'"
|
||||
title="本地存储说明"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
文件将存储在服务器本地磁盘,适合小规模应用或开发测试环境
|
||||
</el-alert>
|
||||
|
||||
<el-alert
|
||||
v-if="formData.storage_type === 'qiniu'"
|
||||
title="七牛云存储说明"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<p>使用七牛云存储需要:</p>
|
||||
<ul style="margin: 8px 0; padding-left: 20px;">
|
||||
<li>在七牛云控制台创建存储空间(Bucket)</li>
|
||||
<li>获取 AccessKey 和 SecretKey(在个人中心-密钥管理)</li>
|
||||
<li>配置 CDN 加速域名(在存储空间-域名管理)</li>
|
||||
<li>确保域名已备案并完成 CNAME 解析</li>
|
||||
</ul>
|
||||
</el-alert>
|
||||
</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 { Link } from "@element-plus/icons-vue";
|
||||
import { getStorageConfig, saveStorageConfig } from "@/api/sitesettings";
|
||||
|
||||
const STORAGE_KEY = "storage_settings_draft";
|
||||
|
||||
const formRef = ref();
|
||||
const submitting = ref(false);
|
||||
|
||||
const formData = reactive({
|
||||
storage_type: "local",
|
||||
qiniu_access_key: "",
|
||||
qiniu_secret_key: "",
|
||||
qiniu_bucket: "",
|
||||
qiniu_domain: "",
|
||||
qiniu_region: "",
|
||||
});
|
||||
|
||||
const validateQiniuField = (field, fieldName) => {
|
||||
return (_rule, value, callback) => {
|
||||
if (
|
||||
formData.storage_type === "qiniu" &&
|
||||
!String(value || "").trim()
|
||||
) {
|
||||
callback(new Error(`${fieldName}不能为空`));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
};
|
||||
};
|
||||
|
||||
const rules = {
|
||||
qiniu_access_key: [
|
||||
{ validator: validateQiniuField("qiniu_access_key", "AccessKey"), trigger: "blur" },
|
||||
],
|
||||
qiniu_secret_key: [
|
||||
{ validator: validateQiniuField("qiniu_secret_key", "SecretKey"), trigger: "blur" },
|
||||
],
|
||||
qiniu_bucket: [
|
||||
{ validator: validateQiniuField("qiniu_bucket", "Bucket"), trigger: "blur" },
|
||||
],
|
||||
qiniu_domain: [
|
||||
{ validator: validateQiniuField("qiniu_domain", "CDN域名"), 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 loadRemoteConfig = async () => {
|
||||
try {
|
||||
const res = await getStorageConfig();
|
||||
if (res?.code === 200 && res?.data) {
|
||||
Object.assign(formData, {
|
||||
storage_type: res.data.storage_type || "local",
|
||||
qiniu_access_key: res.data.qiniu_access_key || "",
|
||||
qiniu_secret_key: res.data.qiniu_secret_key || "",
|
||||
qiniu_bucket: res.data.qiniu_bucket || "",
|
||||
qiniu_domain: res.data.qiniu_domain || "",
|
||||
qiniu_region: res.data.qiniu_region || "",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载存储配置失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const saveDraft = () => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(formData));
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(formData, {
|
||||
storage_type: "local",
|
||||
qiniu_access_key: "",
|
||||
qiniu_secret_key: "",
|
||||
qiniu_bucket: "",
|
||||
qiniu_domain: "",
|
||||
qiniu_region: "",
|
||||
});
|
||||
saveDraft();
|
||||
ElMessage.success("已重置");
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return;
|
||||
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
await saveStorageConfig({
|
||||
storage_type: formData.storage_type,
|
||||
qiniu_access_key: formData.qiniu_access_key || null,
|
||||
qiniu_secret_key: formData.qiniu_secret_key || null,
|
||||
qiniu_bucket: formData.qiniu_bucket || null,
|
||||
qiniu_domain: formData.qiniu_domain || null,
|
||||
qiniu_region: formData.qiniu_region || null,
|
||||
});
|
||||
saveDraft();
|
||||
ElMessage.success("保存成功");
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.response?.data?.msg || "保存失败");
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadDraft();
|
||||
loadRemoteConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.storage-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-top: 4px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-alert) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
:deep(.el-alert ul) {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
:deep(.el-alert li) {
|
||||
line-height: 1.8;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="storage-settings">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="140px"
|
||||
class="settings-form"
|
||||
>
|
||||
<el-card shadow="never" class="settings-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>存储位置配置</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form-item label="存储类型" prop="storage_type">
|
||||
<el-radio-group v-model="formData.storage_type">
|
||||
<el-radio label="local">本地存储</el-radio>
|
||||
<el-radio label="qiniu">七牛云存储</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="formData.storage_type === 'qiniu'">
|
||||
<el-divider content-position="left">七牛云配置</el-divider>
|
||||
|
||||
<el-form-item label="AccessKey" prop="qiniu_access_key">
|
||||
<el-input
|
||||
v-model="formData.qiniu_access_key"
|
||||
placeholder="请输入七牛云 AccessKey"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="SecretKey" prop="qiniu_secret_key">
|
||||
<el-input
|
||||
v-model="formData.qiniu_secret_key"
|
||||
type="password"
|
||||
placeholder="请输入七牛云 SecretKey"
|
||||
show-password
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Bucket" prop="qiniu_bucket">
|
||||
<el-input
|
||||
v-model="formData.qiniu_bucket"
|
||||
placeholder="请输入存储空间名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="CDN域名" prop="qiniu_domain">
|
||||
<el-input
|
||||
v-model="formData.qiniu_domain"
|
||||
placeholder="请输入CDN加速域名,如:https://cdn.example.com"
|
||||
clearable
|
||||
>
|
||||
<template #prepend>
|
||||
<el-icon><Link /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="form-tip">用于访问上传的文件,需要在七牛云控制台配置</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="存储区域" prop="qiniu_region">
|
||||
<el-select
|
||||
v-model="formData.qiniu_region"
|
||||
placeholder="请选择存储区域"
|
||||
clearable
|
||||
>
|
||||
<el-option label="华东-浙江" value="z0" />
|
||||
<el-option label="华北-河北" value="z1" />
|
||||
<el-option label="华南-广东" value="z2" />
|
||||
<el-option label="北美-洛杉矶" value="na0" />
|
||||
<el-option label="亚太-新加坡" value="as0" />
|
||||
<el-option label="华东-浙江2" value="cn-east-2" />
|
||||
</el-select>
|
||||
<div class="form-tip">请根据您的Bucket所在区域选择</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-alert
|
||||
v-if="formData.storage_type === 'local'"
|
||||
title="本地存储说明"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
文件将存储在服务器本地磁盘,适合小规模应用或开发测试环境
|
||||
</el-alert>
|
||||
|
||||
<el-alert
|
||||
v-if="formData.storage_type === 'qiniu'"
|
||||
title="七牛云存储说明"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<p>使用七牛云存储需要:</p>
|
||||
<ul style="margin: 8px 0; padding-left: 20px;">
|
||||
<li>在七牛云控制台创建存储空间(Bucket)</li>
|
||||
<li>获取 AccessKey 和 SecretKey(在个人中心-密钥管理)</li>
|
||||
<li>配置 CDN 加速域名(在存储空间-域名管理)</li>
|
||||
<li>确保域名已备案并完成 CNAME 解析</li>
|
||||
</ul>
|
||||
</el-alert>
|
||||
</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 { Link } from "@element-plus/icons-vue";
|
||||
import { getStorageConfig, saveStorageConfig } from "@/api/sitesettings";
|
||||
|
||||
const STORAGE_KEY = "storage_settings_draft";
|
||||
|
||||
const formRef = ref();
|
||||
const submitting = ref(false);
|
||||
|
||||
const formData = reactive({
|
||||
storage_type: "local",
|
||||
qiniu_access_key: "",
|
||||
qiniu_secret_key: "",
|
||||
qiniu_bucket: "",
|
||||
qiniu_domain: "",
|
||||
qiniu_region: "",
|
||||
});
|
||||
|
||||
const validateQiniuField = (field, fieldName) => {
|
||||
return (_rule, value, callback) => {
|
||||
if (
|
||||
formData.storage_type === "qiniu" &&
|
||||
!String(value || "").trim()
|
||||
) {
|
||||
callback(new Error(`${fieldName}不能为空`));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
};
|
||||
};
|
||||
|
||||
const rules = {
|
||||
qiniu_access_key: [
|
||||
{ validator: validateQiniuField("qiniu_access_key", "AccessKey"), trigger: "blur" },
|
||||
],
|
||||
qiniu_secret_key: [
|
||||
{ validator: validateQiniuField("qiniu_secret_key", "SecretKey"), trigger: "blur" },
|
||||
],
|
||||
qiniu_bucket: [
|
||||
{ validator: validateQiniuField("qiniu_bucket", "Bucket"), trigger: "blur" },
|
||||
],
|
||||
qiniu_domain: [
|
||||
{ validator: validateQiniuField("qiniu_domain", "CDN域名"), 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 loadRemoteConfig = async () => {
|
||||
try {
|
||||
const res = await getStorageConfig();
|
||||
if (res?.code === 200 && res?.data) {
|
||||
Object.assign(formData, {
|
||||
storage_type: res.data.storage_type || "local",
|
||||
qiniu_access_key: res.data.qiniu_access_key || "",
|
||||
qiniu_secret_key: res.data.qiniu_secret_key || "",
|
||||
qiniu_bucket: res.data.qiniu_bucket || "",
|
||||
qiniu_domain: res.data.qiniu_domain || "",
|
||||
qiniu_region: res.data.qiniu_region || "",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载存储配置失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const saveDraft = () => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(formData));
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(formData, {
|
||||
storage_type: "local",
|
||||
qiniu_access_key: "",
|
||||
qiniu_secret_key: "",
|
||||
qiniu_bucket: "",
|
||||
qiniu_domain: "",
|
||||
qiniu_region: "",
|
||||
});
|
||||
saveDraft();
|
||||
ElMessage.success("已重置");
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return;
|
||||
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
await saveStorageConfig({
|
||||
storage_type: formData.storage_type,
|
||||
qiniu_access_key: formData.qiniu_access_key || null,
|
||||
qiniu_secret_key: formData.qiniu_secret_key || null,
|
||||
qiniu_bucket: formData.qiniu_bucket || null,
|
||||
qiniu_domain: formData.qiniu_domain || null,
|
||||
qiniu_region: formData.qiniu_region || null,
|
||||
});
|
||||
saveDraft();
|
||||
ElMessage.success("保存成功");
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.response?.data?.msg || "保存失败");
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadDraft();
|
||||
loadRemoteConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.storage-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-top: 4px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-alert) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
:deep(.el-alert ul) {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
:deep(.el-alert li) {
|
||||
line-height: 1.8;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
<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-tab-pane label="存储配置" name="storage">
|
||||
<storageSettings
|
||||
ref="storageSettingsRef"
|
||||
v-if="activeTab === 'storage'"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 通知配置 -->
|
||||
<el-tab-pane label="通知配置" name="notification">
|
||||
<notificationSettings
|
||||
ref="notificationSettingsRef"
|
||||
v-if="activeTab === 'notification'"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import platformSettings from "./components/platformSettings.vue";
|
||||
import storageSettings from "./components/storageSettings.vue";
|
||||
import notificationSettings from "./components/notificationSettings.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>
|
||||
<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-tab-pane label="存储配置" name="storage">
|
||||
<storageSettings
|
||||
ref="storageSettingsRef"
|
||||
v-if="activeTab === 'storage'"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 通知配置 -->
|
||||
<el-tab-pane label="通知配置" name="notification">
|
||||
<notificationSettings
|
||||
ref="notificationSettingsRef"
|
||||
v-if="activeTab === 'notification'"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import platformSettings from "./components/platformSettings.vue";
|
||||
import storageSettings from "./components/storageSettings.vue";
|
||||
import notificationSettings from "./components/notificationSettings.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>
|
||||
|
||||
Reference in New Issue
Block a user