first commit

This commit is contained in:
2026-06-03 10:09:03 +08:00
commit 81f5039458
6266 changed files with 188236 additions and 0 deletions
@@ -0,0 +1,92 @@
<template>
<el-form ref="contactFormRef" :model="contactForm" :rules="contactRules" label-width="120px" style="max-width: 600px">
<el-form-item label="联系电话" prop="phone">
<el-input v-model="contactForm.phone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="联系邮箱" prop="email">
<el-input v-model="contactForm.email" placeholder="请输入联系邮箱" />
</el-form-item>
<el-form-item label="公司地址" prop="address">
<el-input v-model="contactForm.address" placeholder="请输入公司地址" />
</el-form-item>
<el-form-item label="工作时间" prop="workTime">
<el-input v-model="contactForm.workTime" placeholder="如:周一至周五 9:00-18:00" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveContactSettings">保存设置</el-button>
<el-button @click="resetContactForm">重置</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue";
import { ElMessage } from "element-plus";
import type { FormInstance, FormRules } from "element-plus";
import { getCompanyInfos, saveCompanyInfos } from "@/api/sitesettings";
import { useAuthStore } from "@/stores/auth";
const authStore = useAuthStore();
const contactFormRef = ref<FormInstance>();
const contactForm = reactive({
phone: "",
email: "",
address: "",
workTime: ""
});
const contactRules: FormRules = {
email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: "blur" }]
};
// 获取企业信息
const initCompanyInfos = async () => {
const res = await getCompanyInfos(authStore.user.tid);
if (res.code === 200 && res.data) {
const data = res.data;
contactForm.phone = data.contact_phone || "";
contactForm.email = data.contact_email || "";
contactForm.address = data.address || "";
contactForm.workTime = data.worktime || "";
}
};
const saveContactSettings = async () => {
if (!contactFormRef.value) return;
await contactFormRef.value.validate(async (valid) => {
if (valid) {
const data = {
tid: authStore.user.tid,
contact_phone: contactForm.phone,
contact_email: contactForm.email,
address: contactForm.address,
worktime: contactForm.workTime
};
const res = await saveCompanyInfos(data);
if (res.code === 200) {
ElMessage.success("联系方式保存成功");
} else {
ElMessage.error(res.msg || "保存失败");
}
}
});
};
const resetContactForm = () => {
contactForm.phone = "";
contactForm.email = "";
contactForm.address = "";
contactForm.workTime = "";
};
defineExpose({
contactForm
});
// 初始化数据
onMounted(() => {
initCompanyInfos();
});
</script>
@@ -0,0 +1,102 @@
<template>
<el-form
ref="legalNoticeFormRef"
:model="formData"
label-width="120px"
style="max-width: 600px"
>
<el-form-item label="法律声明" prop="legalNotice">
<el-input
v-model="legalNotice"
type="textarea"
:rows="8"
placeholder="请输入法律声明"
/>
</el-form-item>
<el-form-item label="隐私条款" prop="privacyTerms">
<el-input
v-model="privacyTerms"
type="textarea"
:rows="8"
placeholder="请输入隐私条款"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSaveLegalInfos">保存设置</el-button>
<el-button @click="resetLegalNoticeForm">重置</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue";
import { ElMessage } from "element-plus";
import type { FormInstance, FormRules } from "element-plus";
import { getLegalInfos, saveLegalInfos } from "@/api/sitesettings";
import { useAuthStore } from "@/stores/auth";
const authStore = useAuthStore();
const legalNoticeFormRef = ref<FormInstance>();
const legalNotice = ref("");
const privacyTerms = ref("");
const seoForm = reactive({
legalNotice: "",
privacyTerms: "",
});
const formData = {
legalNotice,
privacyTerms,
};
//调用法律声明和隐私条款数据
const initLegalInfos = async () => {
const res = await getLegalInfos();
if (res.code === 200 && res.data) {
const data = res.data;
const dataMap: Record<string, string> = {};
data.forEach((item: any) => {
dataMap[item.label] = item.value;
});
legalNotice.value = dataMap["legalNotice"] || "";
privacyTerms.value = dataMap["privacyTerms"] || "";
}
};
const handleSaveLegalInfos = async () => {
if (!legalNoticeFormRef.value) return;
await legalNoticeFormRef.value.validate(async (valid) => {
if (valid) {
const data = {
tid: authStore.user.tid,
legalNotice: legalNotice.value,
privacyTerms: privacyTerms.value
};
const res = await saveLegalInfos(data);
if (res.code === 200) {
ElMessage.success("保存成功");
} else {
ElMessage.error(res.msg || "保存失败");
}
}
});
};
const resetLegalNoticeForm = () => {
legalNotice.value = "";
privacyTerms.value = "";
};
defineExpose({
legalNotice,
privacyTerms,
});
// 初始化法律声明和隐私条款数据
onMounted(() => {
initLegalInfos();
});
</script>
@@ -0,0 +1,81 @@
<template>
<el-form ref="formRef" :model="form" label-width="120px">
<el-form-item label="开启验证" prop="openVerify">
<el-switch v-model="form.openVerify" />
</el-form-item>
<el-form-item label="验证模式" prop="verifyModel">
<el-radio-group v-model="form.verifyModel" :disabled="!form.openVerify">
<el-radio-button value="1">图形</el-radio-button>
<el-radio-button value="2">短信</el-radio-button>
<el-radio-button value="3">邮箱</el-radio-button>
<el-radio-button value="4">极验3.0</el-radio-button>
<el-radio-button value="5">极验4.0</el-radio-button>
</el-radio-group>
</el-form-item>
<template v-if="form.verifyModel === '4'">
<el-form-item label="极验ID" prop="geetest3ID">
<el-input v-model="form.geetest3ID" />
</el-form-item>
<el-form-item label="极验KEY" prop="geetest3KEY">
<el-input v-model="form.geetest3KEY" />
</el-form-item>
</template>
<template v-if="form.verifyModel === '5'">
<el-form-item label="极验ID" prop="geetest4ID">
<el-input v-model="form.geetest4ID" />
</el-form-item>
<el-form-item label="极验KEY" prop="geetest4KEY">
<el-input v-model="form.geetest4KEY" />
</el-form-item>
</template>
<el-form-item>
<el-button type="primary" @click="handleSave">保存</el-button>
<el-button @click="formRef?.resetFields()">重置</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import { ElMessage } from "element-plus";
import { getVerifyInfos, saveVerifyInfos } from "@/api/sitesettings";
const formRef = ref();
const form = reactive({
openVerify: false,
verifyModel: "1",
geetest3ID: "",
geetest3KEY: "",
geetest4ID: "",
geetest4KEY: ""
});
const init = async () => {
const { code, data } = await getVerifyInfos();
if (code === 200 && Array.isArray(data)) {
const formData = {};
data.forEach(item => {
if (item.label === 'openVerify') {
formData[item.label] = item.value === '1';
} else {
formData[item.label] = item.value;
}
});
Object.assign(form, formData);
}
};
const handleSave = async () => {
const { code } = await saveVerifyInfos(form);
if (code === 200) ElMessage.success("保存成功");
};
init();
defineExpose({ form, init });
</script>
@@ -0,0 +1,301 @@
<template>
<el-form
ref="normalFormRef"
:model="formData"
:rules="normalRules"
label-width="120px"
style="max-width: 600px"
>
<el-form-item label="站点名称" prop="sitename">
<el-input v-model="sitename" placeholder="请输入站点名称" />
</el-form-item>
<el-form-item label="站点Logo" prop="logo">
<el-upload
class="logo-uploader"
:show-file-list="false"
:auto-upload="false"
:on-change="handleLogoChange"
>
<img
v-if="logo"
:src="getPreviewUrl(logo)"
class="logo-image"
/>
<el-icon v-else class="logo-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="站点Logo-浅色" prop="logow">
<el-upload
class="logo-uploader"
:show-file-list="false"
:auto-upload="false"
:on-change="handleLogowChange"
>
<img
v-if="logow"
:src="getPreviewUrl(logow)"
class="logo-image darkbg"
/>
<el-icon v-else class="logo-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="站点图标" prop="ico">
<el-upload
class="logo-uploader"
:show-file-list="false"
:auto-upload="false"
:on-change="handleIconChange"
>
<img
v-if="ico"
:src="getPreviewUrl(ico)"
class="logo-image"
/>
<el-icon v-else class="logo-uploader-icon"><Plus /></el-icon>
</el-upload>
<div class="form-tip">请上传ico格式文件</div>
</el-form-item>
<el-form-item label="企业名称" prop="companyname">
<el-input v-model="companyname" placeholder="请输入企业名称" />
</el-form-item>
<el-form-item label="企业介绍" prop="companyintroduction">
<el-input v-model="companyintroduction" type="textarea" rows="5" placeholder="请输入企业介绍" />
</el-form-item>
<el-form-item label="站点简介" prop="description">
<el-input v-model="description" placeholder="请输入站点简介" />
</el-form-item>
<el-form-item label="版权信息" prop="copyright">
<el-input v-model="copyright" placeholder="如:© 2026 公司名称" />
</el-form-item>
<el-form-item label="备案号 " prop="icp">
<el-input v-model="icp" placeholder="如:苏ICP备20260000号" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSaveNormalInfos"
>保存设置</el-button
>
<el-button @click="resetnormalForm">重置</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { ElMessage } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import type { FormInstance, FormRules, UploadFile } from "element-plus";
import { getNormalInfos, saveNormalInfos } from "@/api/sitesettings";
import { uploadFile } from "@/api/file";
import { useAuthStore } from "@/stores/auth";
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
const authStore = useAuthStore();
const normalFormRef = ref<FormInstance>();
const sitename = ref("");
const companyname = ref("");
const companyintroduction = ref("");
const logo = ref("");
const logow = ref("");
const description = ref("");
const copyright = ref("");
const icp = ref("");
const ico = ref("");
const formData = {
sitename,
companyname,
companyintroduction,
logo,
logow,
ico,
description,
copyright,
icp,
};
const normalRules: FormRules = {
sitename: [{ required: true, message: "请输入站点名称", trigger: "blur" }],
};
const getPreviewUrl = (url: string) => {
const value = String(url || "").trim().replace(/\\/g, "/");
if (!value) return "";
if (/^(https?:\/\/|\/\/|data:|blob:)/i.test(value)) {
return value;
}
const base = String(API_BASE_URL || "").replace(/\/+$/, "");
const path = value.startsWith("/") ? value : `/${value}`;
return `${base}${path}`;
};
//调用基础数据
const initNormalInfos = async () => {
const res = await getNormalInfos(authStore.user.tid);
if (res.code === 200 && res.data) {
const data = res.data;
sitename.value = data.sitename || "";
companyintroduction.value = data.companyintroduction || "";
logo.value = data.logo || "";
logow.value = data.logow || "";
ico.value = data.ico || "";
description.value = data.description || "";
copyright.value = data.copyright || "";
icp.value = data.icp || "";
companyname.value = data.companyname || "";
}
};
const handleLogoChange = (file: UploadFile) => {
const uploadFormData = new FormData();
uploadFormData.append("file", file.raw);
uploadFormData.append("cate", "site");
uploadFile(uploadFormData).then((uploadRes) => {
if (
(uploadRes.code === 200 || uploadRes.code === 201) &&
uploadRes.data &&
uploadRes.data.url
) {
logo.value = uploadRes.data.url.replace(/\\/g, "/");
} else {
ElMessage.error(uploadRes.msg || "上传失败");
}
});
};
const handleLogowChange = (file: UploadFile) => {
const uploadFormData = new FormData();
uploadFormData.append("file", file.raw);
uploadFormData.append("cate", "site");
uploadFile(uploadFormData).then((uploadRes) => {
if (
(uploadRes.code === 200 || uploadRes.code === 201) &&
uploadRes.data &&
uploadRes.data.url
) {
logow.value = uploadRes.data.url.replace(/\\/g, "/");
} else {
ElMessage.error(uploadRes.msg || "上传失败");
}
});
};
const handleSaveNormalInfos = async () => {
if (!normalFormRef.value) return;
await normalFormRef.value.validate(async (valid) => {
if (valid) {
const data = {
tid: authStore.user.tid,
sitename: sitename.value,
companyintroduction: companyintroduction.value,
logo: logo.value,
logow: logow.value,
ico: ico.value,
description: description.value,
companyname: companyname.value,
copyright: copyright.value,
icp: icp.value,
};
const res = await saveNormalInfos(data);
if (res.code === 200) {
ElMessage.success("保存成功");
} else {
ElMessage.error(res.msg || "保存失败");
}
}
});
};
const handleIconChange = (file: UploadFile) => {
const uploadFormData = new FormData();
uploadFormData.append("file", file.raw);
uploadFormData.append("cate", "site");
uploadFile(uploadFormData).then((uploadRes) => {
if ((uploadRes.code === 200 || uploadRes.code === 201) && uploadRes.data?.url) {
ico.value = uploadRes.data.url.replace(/\\/g, "/");
} else {
ElMessage.error(uploadRes.msg || "上传失败");
}
});
};
const resetnormalForm = () => {
sitename.value = "";
companyname.value = "";
companyintroduction.value = "";
logo.value = "";
logow.value = "";
ico.value = "";
description.value = "";
copyright.value = "";
icp.value = "";
};
defineExpose({
sitename,
companyname,
companyintroduction,
logo,
logow,
ico,
description,
copyright,
icp,
});
// 初始化基础数据
onMounted(() => {
initNormalInfos();
});
</script>
<style lang="less" scoped>
.darkbg {
background-color: #efefef;
}
.form-tip{
margin-left: 20px;
color: #999;
font-size: 12px;
margin-top: 5px;
}
.logo-uploader {
:deep(.el-upload) {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: all 0.3s;
&:hover {
border-color: #3973ff;
}
}
.logo-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100px;
height: 100px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.logo-image {
width: 100px;
height: 100px;
object-fit: contain;
display: block;
}
}
</style>
@@ -0,0 +1,62 @@
<template>
<el-form
ref="otherFormRef"
:model="otherForm"
label-width="120px"
style="max-width: 600px"
>
<el-form-item label="开启维护模式">
<el-switch v-model="otherForm.maintenanceMode" />
<span class="form-tip">开启后,前台将显示维护中页面</span>
</el-form-item>
<el-form-item label="开启注册">
<el-switch v-model="otherForm.allowRegister" />
<span class="form-tip">允许用户在前台注册账号</span>
</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-button type="primary" @click="saveOtherSettings">保存设置</el-button>
<el-button @click="resetOtherForm">重置</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import { ElMessage } from "element-plus";
import type { FormInstance } from "element-plus";
const otherFormRef = ref<FormInstance>();
const otherForm = reactive({
maintenanceMode: false,
allowRegister: false,
captchaEnabled: true
});
const saveOtherSettings = () => {
// TODO: 保存其他设置
ElMessage.success("其他设置保存成功");
};
const resetOtherForm = () => {
otherForm.maintenanceMode = false;
otherForm.allowRegister = false;
otherForm.captchaEnabled = true;
};
defineExpose({
otherForm
});
</script>
<style lang="less" scoped>
.form-tip {
margin-left: 10px;
font-size: 12px;
color: #909399;
}
</style>
@@ -0,0 +1,101 @@
<template>
<el-form
ref="seoFormRef"
:model="seoForm"
:rules="seoRules"
label-width="120px"
style="max-width: 600px"
>
<el-form-item label="SEO标题" prop="seoTitle">
<el-input v-model="seoForm.seoTitle" placeholder="请输入SEO标题" />
</el-form-item>
<el-form-item label="SEO关键词" prop="seoKeywords">
<el-input
v-model="seoForm.seoKeywords"
placeholder="多个关键词用逗号分隔"
/>
</el-form-item>
<el-form-item label="SEO描述" prop="seoDescription">
<el-input
v-model="seoForm.seoDescription"
type="textarea"
:rows="3"
placeholder="请输入SEO描述"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveSeoSettings">保存设置</el-button>
<el-button @click="resetSeoForm">重置</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElLoading } from "element-plus";
import type { FormInstance, FormRules } from "element-plus";
import { getCompanySeo, saveCompanySeo } from "@/api/sitesettings";
const seoFormRef = ref<FormInstance>();
const seoForm = reactive({
seoTitle: "",
seoKeywords: "",
seoDescription: "",
});
const seoRules: FormRules = {
seoTitle: [{ required: true, message: "请输入SEO标题", trigger: "blur" }],
};
const fetchSeoData = async () => {
try {
const res = await getCompanySeo();
if (res.code === 200) {
Object.assign(seoForm, res.data);
}
} catch (error) {
ElMessage.error("获取SEO数据失败");
}
};
const saveSeoSettings = async () => {
if (!seoFormRef.value) return;
await seoFormRef.value.validate(async (valid) => {
if (valid) {
const loading = ElLoading.service({ text: "正在保存..." });
try {
const res = await saveCompanySeo({
seoTitle: seoForm.seoTitle,
seoKeywords: seoForm.seoKeywords,
seoDescription: seoForm.seoDescription,
});
if (res.code === 200) {
ElMessage.success("SEO设置保存成功");
} else {
ElMessage.error(res.msg);
}
} catch (error) {
ElMessage.error("网络请求失败");
} finally {
loading.close();
}
}
});
};
const resetSeoForm = () => {
if (!seoFormRef.value) return;
seoFormRef.value.resetFields();
};
onMounted(() => {
fetchSeoData();
});
defineExpose({
seoForm,
});
</script>
@@ -0,0 +1,98 @@
<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="basic">
<normalSettings
ref="normalSettingsRef"
v-if="activeTab === 'basic'"
/>
</el-tab-pane>
<el-tab-pane label="SEO设置" name="seo">
<seoSettings ref="seoSettingsRef" v-if="activeTab === 'seo'" />
</el-tab-pane>
<el-tab-pane label="联系方式" name="contact">
<contactSettings
ref="contactSettingsRef"
v-if="activeTab === 'contact'"
/>
</el-tab-pane>
<el-tab-pane label="登录验证" v-if="groupId === 1" name="loginVerification">
<loginVerificationSettings
ref="loginVerificationSettingsRef"
v-if="activeTab === 'loginVerification'"
/>
</el-tab-pane>
<el-tab-pane label="法律声明&隐私条款" name="legalNotice">
<legalNoticeSettings
ref="legalNoticeSettingsRef"
v-if="activeTab === 'legalNotice'"
/>
</el-tab-pane>
<el-tab-pane label="其他设置" name="other">
<otherSettings ref="otherSettingsRef" v-if="activeTab === 'other'" />
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import normalSettings from "./components/normalSettings.vue";
import seoSettings from "./components/seoSettings.vue";
import contactSettings from "./components/contactSettings.vue";
import otherSettings from "./components/otherSettings.vue";
import legalNoticeSettings from "./components/legalNotice.vue";
import loginVerificationSettings from "./components/loginVerification.vue";
import { useAuthStore } from '@/stores/auth';
const authStore = useAuthStore();
const groupId = (authStore.userInfo as any)?.group_id;
const activeTab = ref("basic");
</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>