更新website后端
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// ==================== 官网管理 - 站点设置(平台端) ====================
|
||||
// 数据落在 yz_platform_site_setting(单行配置),字段可被官网模板标签调用:
|
||||
// {yz:sitename} {yz:logo} {yz:logow} {yz:icp} {yz:policeicp} {yz:copyright} {yz:privacy} {yz:terms}
|
||||
|
||||
// 读取站点设置
|
||||
export function getWebsiteSetting() {
|
||||
return request({
|
||||
url: '/platform/website/setting/info',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 保存站点设置
|
||||
export function saveWebsiteSetting(data) {
|
||||
return request({
|
||||
url: '/platform/website/setting/save',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
@@ -27,6 +27,12 @@ const staticMainChildren = [
|
||||
component: () => import("@/views/tools/passwordStore/index.vue"),
|
||||
meta: { requiresAuth: true, title: "密码存储" }
|
||||
},
|
||||
{
|
||||
path: "/website/setting",
|
||||
name: "PlatformWebsiteSetting",
|
||||
component: () => import("@/views/website/setting/index.vue"),
|
||||
meta: { requiresAuth: true, title: "站点设置" }
|
||||
},
|
||||
{
|
||||
path: "/template/index",
|
||||
name: "PlatformTemplateManage",
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="tag-tip">
|
||||
<span class="tag-tip__label">调用标签</span>
|
||||
<code class="tag-tip__code">{{ tag }}</code>
|
||||
<el-button link type="primary" size="small" @click="copy">复制</el-button>
|
||||
<span v-if="desc" class="tag-tip__desc">{{ desc }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
// 标签代码,如 {yz:sitename}
|
||||
tag: { type: String, required: true },
|
||||
// 用法补充说明
|
||||
desc: { type: String, default: '' }
|
||||
})
|
||||
|
||||
const copy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(props.tag)
|
||||
ElMessage.success('已复制')
|
||||
} catch (e) {
|
||||
ElMessage.error('复制失败,请手动复制')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tag-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
|
||||
&__label {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
&__code {
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
background: #f4f4f5;
|
||||
color: #3973ff;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
color: #a8abb2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="130px"
|
||||
class="tab-form"
|
||||
>
|
||||
<el-form-item label="站点名称" prop="sitename">
|
||||
<el-input v-model="form.sitename" placeholder="请输入站点名称" />
|
||||
<TagTip tag="{yz:sitename}" desc="常用于 <title> 和页头" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="副标题" prop="subtitle">
|
||||
<el-input v-model="form.subtitle" placeholder="请输入副标题" />
|
||||
<TagTip tag="{yz:subtitle}" desc="站点名称下方的补充说明,常用于页头/首页" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Logo(深色)" prop="logo">
|
||||
<el-upload
|
||||
class="logo-uploader"
|
||||
:show-file-list="false"
|
||||
:auto-upload="false"
|
||||
:on-change="(file) => handleUpload(file, 'logo')"
|
||||
>
|
||||
<img v-if="form.logo" :src="previewUrl(form.logo)" class="logo-image" />
|
||||
<el-icon v-else class="logo-uploader-icon"><Plus /></el-icon>
|
||||
</el-upload>
|
||||
<TagTip tag="{yz:logo}" desc="深色 Logo,用于浅色背景(如页头)" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Logo(浅色)" prop="logow">
|
||||
<el-upload
|
||||
class="logo-uploader"
|
||||
:show-file-list="false"
|
||||
:auto-upload="false"
|
||||
:on-change="(file) => handleUpload(file, 'logow')"
|
||||
>
|
||||
<img
|
||||
v-if="form.logow"
|
||||
:src="previewUrl(form.logow)"
|
||||
class="logo-image darkbg"
|
||||
/>
|
||||
<el-icon v-else class="logo-uploader-icon"><Plus /></el-icon>
|
||||
</el-upload>
|
||||
<TagTip tag="{yz:logow}" desc="浅色 Logo,用于深色背景(如页脚)" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="handleSave">保存设置</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { uploadFile } from '@/api/file'
|
||||
import TagTip from './TagTip.vue'
|
||||
|
||||
const props = defineProps({
|
||||
// 与父组件共享的表单对象(key 即标签名)
|
||||
form: { type: Object, required: true },
|
||||
saving: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['save', 'reset'])
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL
|
||||
const formRef = ref(null)
|
||||
|
||||
const rules = {
|
||||
sitename: [{ required: true, message: '请输入站点名称', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
/** 图片预览地址:完整 URL 直接使用,站内相对路径拼接后端地址 */
|
||||
const previewUrl = (url) => {
|
||||
if (!url) return ''
|
||||
if (/^https?:\/\//i.test(url)) return url
|
||||
return API_BASE_URL + url.replace(/^\//, '/')
|
||||
}
|
||||
|
||||
// Logo 上传:上传成功后回填图片地址
|
||||
const handleUpload = (file, field) => {
|
||||
const fd = new FormData()
|
||||
fd.append('file', file.raw)
|
||||
fd.append('cate', 'site')
|
||||
|
||||
uploadFile(fd).then((res) => {
|
||||
if ((res.code === 200 || res.code === 201) && res.data && res.data.url) {
|
||||
props.form[field] = res.data.url.replace(/\\/g, '/')
|
||||
} else {
|
||||
ElMessage.error(res.msg || '上传失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate((valid) => {
|
||||
if (valid) emit('save')
|
||||
})
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
formRef.value?.clearValidate()
|
||||
emit('reset')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tab-form {
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.darkbg {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
.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,66 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="form" label-width="130px" class="tab-form">
|
||||
<el-form-item label="公司名称" prop="companyname">
|
||||
<el-input v-model="form.companyname" placeholder="请输入公司名称" />
|
||||
<TagTip tag="{yz:companyname}" desc="公司全称,常用于页脚与「关于我们」" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="联系电话" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="如:400-000-0000" />
|
||||
<TagTip tag="{yz:phone}" desc="服务热线,常用于页头与页脚" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="电子邮箱" prop="email">
|
||||
<el-input v-model="form.email" placeholder="如:contact@yunzer.com" />
|
||||
<TagTip tag="{yz:email}" desc="商务合作邮箱,可配合 mailto: 使用" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="公司地址" prop="address">
|
||||
<el-input
|
||||
v-model="form.address"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入公司地址"
|
||||
/>
|
||||
<TagTip tag="{yz:address}" desc="公司所在地址,展示于页脚/联系页面" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="工作时间" prop="worktime">
|
||||
<el-input v-model="form.worktime" placeholder="如:工作日 9:00 - 18:00" />
|
||||
<TagTip tag="{yz:worktime}" desc="常用于页脚与联系我们页面" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="handleSave">保存设置</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import TagTip from './TagTip.vue'
|
||||
|
||||
defineProps({
|
||||
// 与父组件共享的表单对象(key 即标签名)
|
||||
form: { type: Object, required: true },
|
||||
saving: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['save', 'reset'])
|
||||
|
||||
const formRef = ref(null)
|
||||
|
||||
const handleSave = () => emit('save')
|
||||
|
||||
const handleReset = () => {
|
||||
formRef.value?.clearValidate()
|
||||
emit('reset')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tab-form {
|
||||
max-width: 720px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="form" label-width="130px" class="tab-form">
|
||||
<el-form-item label="隐私政策" prop="privacy">
|
||||
<el-input v-model="form.privacy" placeholder="如:/policy/privacy" />
|
||||
<TagTip
|
||||
tag="{yz:privacy}"
|
||||
desc="链接地址,模板中用 <a href="{yz:privacy}"> 承接"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="使用条款" prop="terms">
|
||||
<el-input v-model="form.terms" placeholder="如:/policy/terms" />
|
||||
<TagTip
|
||||
tag="{yz:terms}"
|
||||
desc="链接地址,模板中用 <a href="{yz:terms}"> 承接"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="handleSave">保存设置</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import TagTip from './TagTip.vue'
|
||||
|
||||
defineProps({
|
||||
// 与父组件共享的表单对象(key 即标签名)
|
||||
form: { type: Object, required: true },
|
||||
saving: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['save', 'reset'])
|
||||
|
||||
const formRef = ref(null)
|
||||
|
||||
const handleSave = () => emit('save')
|
||||
|
||||
const handleReset = () => {
|
||||
formRef.value?.clearValidate()
|
||||
emit('reset')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tab-form {
|
||||
max-width: 720px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="form" label-width="130px" class="tab-form">
|
||||
<el-form-item label="ICP 备案号" prop="icp">
|
||||
<el-input v-model="form.icp" placeholder="如:苏ICP备2026000000号-1" />
|
||||
<TagTip tag="{yz:icp}" desc="常见于页脚,请填写真实备案号" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="公安备案" prop="policeicp">
|
||||
<el-input v-model="form.policeicp" placeholder="如:苏公网安备32010000000000号" />
|
||||
<TagTip tag="{yz:policeicp}" desc="公安网备案号,常见于页脚" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="版权信息" prop="copyright">
|
||||
<el-input v-model="form.copyright" placeholder="如:© 2026 公司名称 版权所有" />
|
||||
<TagTip tag="{yz:copyright}" desc="常用于页脚" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="handleSave">保存设置</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import TagTip from './TagTip.vue'
|
||||
|
||||
defineProps({
|
||||
// 与父组件共享的表单对象(key 即标签名)
|
||||
form: { type: Object, required: true },
|
||||
saving: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['save', 'reset'])
|
||||
|
||||
const formRef = ref(null)
|
||||
|
||||
const handleSave = () => emit('save')
|
||||
|
||||
const handleReset = () => {
|
||||
formRef.value?.clearValidate()
|
||||
emit('reset')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tab-form {
|
||||
max-width: 720px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div class="container-box website-setting">
|
||||
<div class="header-bar">
|
||||
<h2>站点设置</h2>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
class="tips"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="每一项都对应一个官网模板标签 {yz:标签名},数据保存在 yz_platform_site_setting(通用键值表,key 即标签名)。"
|
||||
/>
|
||||
|
||||
<div class="setting-container" v-loading="loading">
|
||||
<el-tabs v-model="activeTab" class="setting-tabs">
|
||||
<el-tab-pane label="基本设置" name="basic">
|
||||
<tab-basic
|
||||
v-if="activeTab === 'basic'"
|
||||
:form="form"
|
||||
:saving="saving"
|
||||
@save="handleSave"
|
||||
@reset="resetForm"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="公司信息" name="company">
|
||||
<tab-company
|
||||
v-if="activeTab === 'company'"
|
||||
:form="form"
|
||||
:saving="saving"
|
||||
@save="handleSave"
|
||||
@reset="resetForm"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="备案信息" name="record">
|
||||
<tab-record
|
||||
v-if="activeTab === 'record'"
|
||||
:form="form"
|
||||
:saving="saving"
|
||||
@save="handleSave"
|
||||
@reset="resetForm"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="政策条款" name="policy">
|
||||
<tab-policy
|
||||
v-if="activeTab === 'policy'"
|
||||
:form="form"
|
||||
:saving="saving"
|
||||
@save="handleSave"
|
||||
@reset="resetForm"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getWebsiteSetting, saveWebsiteSetting } from '@/api/websiteSetting'
|
||||
import tabBasic from './components/tab_basic.vue'
|
||||
import tabCompany from './components/tab_company.vue'
|
||||
import tabRecord from './components/tab_record.vue'
|
||||
import tabPolicy from './components/tab_policy.vue'
|
||||
|
||||
const activeTab = ref('basic')
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
// 表单字段的 key 与数据表的 tag 一一对应(提交时即为 { 标签名: 值 })
|
||||
const form = reactive({
|
||||
// 基本设置
|
||||
sitename: '', // {yz:sitename}
|
||||
subtitle: '', // {yz:subtitle}
|
||||
logo: '', // {yz:logo}
|
||||
logow: '', // {yz:logow}
|
||||
// 公司信息
|
||||
companyname: '', // {yz:companyname}
|
||||
phone: '', // {yz:phone}
|
||||
email: '', // {yz:email}
|
||||
address: '', // {yz:address}
|
||||
worktime: '', // {yz:worktime}
|
||||
// 备案信息
|
||||
icp: '', // {yz:icp}
|
||||
policeicp: '', // {yz:policeicp}
|
||||
copyright: '', // {yz:copyright}
|
||||
// 政策条款
|
||||
privacy: '', // {yz:privacy}
|
||||
terms: '' // {yz:terms}
|
||||
})
|
||||
|
||||
// 拉取站点设置:接口返回 values(标签名 -> 值)回填到表单
|
||||
const initSetting = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getWebsiteSetting()
|
||||
if (res.code === 200 && res.data) {
|
||||
const data = res.data.values || {}
|
||||
Object.keys(form).forEach((key) => {
|
||||
form[key] = data[key] || ''
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取站点设置失败')
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('获取站点设置失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存:整个表单按「标签名 -> 值」提交,后端按 tag 逐项 upsert
|
||||
const handleSave = async () => {
|
||||
if (!String(form.sitename || '').trim()) {
|
||||
ElMessage.warning('站点名称不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
const res = await saveWebsiteSetting({ ...form })
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('保存成功')
|
||||
} else {
|
||||
ElMessage.error(res.msg || '保存失败')
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置:丢弃本地改动,重新拉取服务端配置
|
||||
const resetForm = () => {
|
||||
initSetting()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initSetting()
|
||||
})
|
||||
</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;
|
||||
}
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.setting-container {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.setting-tabs {
|
||||
:deep(.el-tabs__header) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-wrap::after) {
|
||||
height: 1px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user