Files
BlackFruit-UI/plugins/addon/theme_configurator/template/admin/index_new.html
2025-12-28 13:57:10 +08:00

914 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<link rel="stylesheet" href="/plugins/addon/theme_configurator/template/admin/theme.css" />
<div id="theme-config-app" class="admin-container" v-cloak>
<section id="basic" class="config-section" :class="{active: activeSection === 'basic'}">
<div class="section-card" data-title="企业信息">
<div class="form-grid">
<div class="form-item">
<label>企业名称</label>
<t-input v-model="fullConfig.site_config.enterprise_name" placeholder="主题云"></t-input>
</div>
<div class="form-item">
<label>联系电话</label>
<t-input v-model="fullConfig.site_config.enterprise_telephone" placeholder="400-000-0000"></t-input>
</div>
<div class="form-item">
<label>联系邮箱</label>
<t-input v-model="fullConfig.site_config.enterprise_mailbox" placeholder="support@example.com"></t-input>
</div>
<div class="form-item form-item--full">
<label>Logo 地址</label>
<div class="upload-row">
<t-input v-model="fullConfig.site_config.official_website_logo" placeholder="/upload/logo.png"></t-input>
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
@success="(ctx) => handleUpload(['site_config', 'official_website_logo'], ctx)">
<t-button size="small" class="ml-10">
<t-icon name="upload" size="small" /> 上传
</t-button>
</t-upload>
</div>
</div>
<div class="form-item form-item--full">
<label>二维码地址</label>
<div class="upload-row">
<t-input v-model="fullConfig.site_config.enterprise_qrcode" placeholder="/upload/qrcode.png"></t-input>
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
@success="(ctx) => handleUpload(['site_config', 'enterprise_qrcode'], ctx)">
<t-button size="small" class="ml-10">
<t-icon name="upload" size="small" /> 上传
</t-button>
</t-upload>
</div>
</div>
<div class="form-item">
<label>在线客服链接</label>
<t-input v-model="fullConfig.site_config.online_customer_service_link"
placeholder="http://www.test.com"></t-input>
</div>
<div class="form-item">
<label>ICP 号</label>
<t-input v-model="fullConfig.site_config.icp_info" placeholder="京ICP备XXXX号"></t-input>
</div>
<div class="form-item">
<label>ICP 链接</label>
<t-input v-model="fullConfig.site_config.icp_info_link" placeholder="https://beian.miit.gov.cn"></t-input>
</div>
<div class="form-item">
<label>公安备案号</label>
<t-input v-model="fullConfig.site_config.public_security_network_preparation"
placeholder="京公网安备XXXX号"></t-input>
</div>
<div class="form-item">
<label>公安备案链接</label>
<t-input v-model="fullConfig.site_config.public_security_network_preparation_link"
placeholder="https://beian.mps.gov.cn"></t-input>
</div>
<div class="form-item">
<label>电信增值许可证</label>
<t-input v-model="fullConfig.site_config.telecom_appreciation" placeholder="增值电信业务经营许可证"></t-input>
</div>
<div class="form-item">
<label>用户协议链接</label>
<t-input v-model="fullConfig.site_config.terms_service_url" placeholder="/agreement/service.html"></t-input>
</div>
<div class="form-item">
<label>隐私政策链接</label>
<t-input v-model="fullConfig.site_config.terms_privacy_url" placeholder="/agreement/privacy.html"></t-input>
</div>
<div class="form-item">
<label>云产品购买链接</label>
<t-input v-model="fullConfig.site_config.cloud_product_link" placeholder="/cart/goods.htm?id=1"></t-input>
</div>
<div class="form-item">
<label>物理机/DCIM 链接</label>
<t-input v-model="fullConfig.site_config.dcim_product_link" placeholder="/cart/goods.htm?id=2"></t-input>
</div>
<div class="form-item form-item--full">
<label>版权信息</label>
<t-input v-model="fullConfig.site_config.copyright_info" placeholder="© 2025 主题云"></t-input>
</div>
</div>
</div>
<div class="section-card" data-title="侧边浮窗">
<p class="theme-tip">
对应前台右侧悬浮工具条(电话咨询/在线客服/提交工单等),结构与模板中的
<code>side_floating_window</code> 一致。
</p>
<div v-if="!fullConfig.side.length" class="empty-tip">
还没有侧边浮窗,点击下方按钮添加。
</div>
<div class="config-item" v-for="(item, index) in fullConfig.side" :key="'side-' + index">
<div class="config-item__header">
<h4>浮窗 {{ index + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeSide(index)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>名称</label>
<t-input v-model="item.name" placeholder="电话咨询"></t-input>
</div>
<div class="form-item form-item--full">
<label>图标地址</label>
<div class="upload-row">
<t-input v-model="item.icon" placeholder="/upload/side-phone.png"></t-input>
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
@success="(ctx) => handleUpload(['side', index, 'icon'], ctx)">
<t-button size="small" class="ml-10">
<t-icon name="upload" size="small" /> 上传
</t-button>
</t-upload>
</div>
</div>
<div class="form-item form-item--full">
<label>内容(支持 HTML</label>
<t-textarea v-model="item.content" :autosize="{ minRows: 2, maxRows: 4 }"
placeholder="<p>7x24 小时不间断服务</p>"></t-textarea>
</div>
</div>
</div>
<t-button theme="primary" variant="outline" @click="addSide">新增浮窗</t-button>
</div>
<div class="section-card" data-title="反馈类型">
<p class="theme-tip">
对应 <code>/console/v1/feedback</code> 的类型选项ID 需与后端保持一致,仅建议修改名称与描述。
</p>
<div v-if="!fullConfig.feedback_type.length" class="empty-tip">
还没有反馈类型,点击下方按钮添加。
</div>
<div class="config-item" v-for="(item, index) in fullConfig.feedback_type" :key="'feedback-' + index">
<div class="config-item__header">
<h4>类型 {{ index + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeFeedbackType(index)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>类型 ID</label>
<t-input v-model="item.id" placeholder="1"></t-input>
</div>
<div class="form-item">
<label>名称</label>
<t-input v-model="item.name" placeholder="产品建议"></t-input>
</div>
<div class="form-item form-item--full">
<label>描述</label>
<t-input v-model="item.description" placeholder="用于产品体验、功能建议等"></t-input>
</div>
</div>
</div>
<t-button theme="primary" variant="outline" @click="addFeedbackType">新增反馈类型</t-button>
</div>
</section>
<section id="seo" class="config-section" :class="{active: activeSection === 'seo'}">
<div class="section-card" data-title="SEO 设置">
<div class="form-grid">
<div class="form-item">
<label>站点标题</label>
<t-input v-model="fullConfig.seo.title" placeholder="首页标题"></t-input>
</div>
<div class="form-item">
<label>关键词</label>
<t-input v-model="fullConfig.seo.keywords" placeholder="关键词,逗号分隔"></t-input>
</div>
<div class="form-item form-item--full">
<label>描述</label>
<t-textarea v-model="fullConfig.seo.description" :autosize="{ minRows: 2, maxRows: 4 }"
placeholder="站点描述"></t-textarea>
</div>
</div>
</div>
</section>
<section id="home" class="config-section" :class="{active: activeSection === 'home'}">
<div class="section-card" data-title="首页轮播文案">
<div v-if="!bannerList.length" class="empty-tip">
还没有轮播文案,点击下方按钮添加。
</div>
<div class="banner-item" v-for="(banner, index) in bannerList" :key="index">
<div class="banner-item__header">
<h4>轮播 {{ index + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeBanner(index)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>标题</label>
<t-input v-model="banner.title" placeholder="如:弹性算力"></t-input>
</div>
<div class="form-item form-item--full">
<label>图片地址</label>
<div class="upload-row">
<t-input v-model="banner.img" placeholder="/upload/banner-1.png"></t-input>
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
@success="(ctx) => handleUpload(['banner', index, 'img'], ctx)">
<t-button size="small" class="ml-10">
<t-icon name="upload" size="small" /> 上传
</t-button>
</t-upload>
</div>
</div>
<div class="form-item">
<label>描述</label>
<t-input v-model="banner.description" placeholder="一句宣传语"></t-input>
</div>
<div class="form-item form-item--full">
<label>标签</label>
<t-input v-model="banner.tags" placeholder="如:高速,低价,安全(多个标签用逗号分隔)"></t-input>
</div>
<div class="form-item">
<label>跳转链接</label>
<t-input v-model="banner.url" placeholder="/cloud.html"></t-input>
</div>
<div class="form-item form-item--switch">
<label><span class="switch-label-icon">🔗</span>新窗口</label>
<t-switch size="large" v-model="banner.blank"></t-switch>
</div>
<div class="form-item">
<label>按钮文字</label>
<t-input v-model="banner.button_text" placeholder="立即查看"></t-input>
</div>
<div class="form-item">
<label>按钮链接</label>
<t-input v-model="banner.button_link" placeholder="/cloud.html"></t-input>
</div>
<div class="form-item form-item--switch">
<label><span class="switch-label-icon">🔗</span>按钮新窗口</label>
<t-switch size="large" v-model="banner.button_blank"></t-switch>
</div>
</div>
</div>
<t-button theme="primary" variant="outline" @click="addBanner">新增轮播</t-button>
</div>
<div class="section-card" data-title="企业资质与荣誉">
<div v-if="!fullConfig.honor.length" class="empty-tip">
用于首页"荣誉资质"模块honor
</div>
<div class="config-item" v-for="(item, index) in fullConfig.honor" :key="'honor-' + index">
<div class="config-item__header">
<h4>荣誉 {{ index + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeHonor(index)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>名称</label>
<t-input v-model="item.name" placeholder="高新技术企业"></t-input>
</div>
<div class="form-item form-item--full">
<label>图片地址</label>
<div class="upload-row">
<t-input v-model="item.img" placeholder="/upload/honor.png"></t-input>
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1"
@success="(ctx) => handleUpload(['honor', index, 'img'], ctx)">
<t-button size="small" class="ml-10">
<t-icon name="upload" size="small" /> 上传
</t-button>
</t-upload>
</div>
</div>
</div>
</div>
<t-button theme="primary" variant="outline" @click="addHonor">新增荣誉</t-button>
</div>
</section>
<section id="nav" class="config-section" :class="{active: activeSection === 'nav'}">
<div class="section-card" data-title="顶部导航header_nav">
<p class="theme-tip">
控制站点顶部导航栏及其下拉菜单结构。第一个导航项用作 Logo 点击跳转地址。
</p>
<div class="form-grid">
<div class="form-item form-item--full">
<label>Logo 点击跳转地址</label>
<t-input v-model="homeNav.file_address" placeholder="index.html 或 /index.html"></t-input>
</div>
</div>
<h4 class="sub-title mt-10">一级导航</h4>
<div v-if="headerNavList.length <= 1" class="empty-tip">
还没有自定义导航,请点击下方按钮新增。
</div>
<div class="config-item" v-for="(item, index) in headerNavList" :key="'nav-' + index" v-if="index > 0">
<div class="config-item__header">
<h4>导航 {{ index }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeHeaderNav(index)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>名称</label>
<t-input v-model="item.name" placeholder="如:产品"></t-input>
</div>
<div class="form-item">
<label>点击链接(可选)</label>
<t-input v-model="item.file_address" placeholder="/cloud.html"></t-input>
</div>
<div class="form-item form-item--switch">
<label><span class="switch-label-icon">🔗</span>新窗口</label>
<t-switch v-model="item.blank"></t-switch>
</div>
</div>
<h4 class="sub-title mt-10">下拉子菜单</h4>
<div v-if="!getHeaderChildren(item).length" class="empty-tip">
还没有子菜单,点击下方按钮新增。
</div>
<div class="config-item" v-for="(child, cIndex) in getHeaderChildren(item)"
:key="'nav-' + index + '-child-' + cIndex">
<div class="config-item__header">
<h4>子菜单 {{ cIndex + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeHeaderNavChild(index, cIndex)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>名称</label>
<t-input v-model="child.name" placeholder="如:云服务器"></t-input>
</div>
<div class="form-item">
<label>链接地址</label>
<t-input v-model="child.file_address" placeholder="/cloud.html"></t-input>
</div>
<div class="form-item form-item--switch">
<label><span class="switch-label-icon">🔗</span>新窗口</label>
<t-switch v-model="child.blank"></t-switch>
</div>
<div class="form-item form-item--full">
<label>图标地址</label>
<div class="upload-row">
<t-input v-model="child.icon" placeholder="/upload/nav-icon.png"></t-input>
<t-upload theme="custom" :action="uploadUrl" :headers="uploadHeaders"
:format-response="uploadFormatResponse" :show-upload-progress="false" :max="1" @success="
(ctx) => handleUpload(['header_nav', index, 'children', cIndex, 'icon'], ctx)
">
<t-button size="small" class="ml-10">
<t-icon name="upload" size="small" /> 上传
</t-button>
</t-upload>
</div>
</div>
<div class="form-item form-item--full">
<label>描述</label>
<t-textarea v-model="child.description" :autosize="{ minRows: 2, maxRows: 3 }"
placeholder="如:高可用的弹性计算服务"></t-textarea>
</div>
</div>
</div>
<t-button class="mt-10" theme="primary" size="small" variant="outline" @click="addHeaderNavChild(index)">
新增子菜单
</t-button>
</div>
<t-button class="mt-10" theme="primary" variant="outline" @click="addHeaderNav">
新增一级导航
</t-button>
</div>
</section>
<section id="footer" class="config-section" :class="{active: activeSection === 'footer'}">
<div class="section-card" data-title="底部栏目footer_nav">
<p class="theme-tip">
控制首页底部多列链接(如【热门产品】【客户支持】等),结构与模板中的
<code>footer_nav</code> 一致。
</p>
<div v-if="!footerNavList.length" class="empty-tip">
还没有底部栏目,点击下方按钮新增。
</div>
<div class="config-item" v-for="(group, index) in footerNavList" :key="'footer-' + index">
<div class="config-item__header">
<h4>栏目 {{ index + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeFooterNav(index)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>栏目标题</label>
<t-input v-model="group.name" placeholder="如:热门产品"></t-input>
</div>
</div>
<h4 class="sub-title mt-10">栏目链接</h4>
<div v-if="!getFooterChildren(group).length" class="empty-tip">
还没有链接,点击下方按钮新增。
</div>
<div class="config-item" v-for="(link, cIndex) in getFooterChildren(group)"
:key="'footer-' + index + '-child-' + cIndex">
<div class="config-item__header">
<h4>链接 {{ cIndex + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeFooterNavChild(index, cIndex)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>名称</label>
<t-input v-model="link.name" placeholder="如:云服务器"></t-input>
</div>
<div class="form-item">
<label>链接地址</label>
<t-input v-model="link.url" placeholder="/cloud.html"></t-input>
</div>
<div class="form-item form-item--switch">
<label><span class="switch-label-icon">🔗</span>新窗口</label>
<t-switch v-model="link.blank"></t-switch>
</div>
</div>
</div>
<t-button class="mt-10" theme="primary" size="small" variant="outline" @click="addFooterNavChild(index)">
新增链接
</t-button>
</div>
<t-button class="mt-10" theme="primary" variant="outline" @click="addFooterNav">
新增栏目
</t-button>
</div>
<div class="section-card" data-title="友情链接">
<div v-if="!fullConfig.friendly_link.length" class="empty-tip">
还没有友情链接,点击下方按钮添加。
</div>
<div class="config-item" v-for="(item, index) in fullConfig.friendly_link" :key="index">
<div class="config-item__header">
<h4>链接 {{ index + 1 }}</h4>
<t-button size="small" theme="danger" variant="outline" @click="removeFriendlyLink(index)">
删除
</t-button>
</div>
<div class="form-grid">
<div class="form-item">
<label>名称</label>
<t-input v-model="item.name" placeholder="合作伙伴名称"></t-input>
</div>
<div class="form-item form-item--full">
<label>链接地址</label>
<t-input v-model="item.url" placeholder="https://example.com"></t-input>
</div>
</div>
</div>
<t-button theme="primary" variant="outline" @click="addFriendlyLink">新增友情链接</t-button>
</div>
</section>
<t-tab-panel v-if="showAdvanced" value="advanced" label="高级设置(JSON)">
<div class="section-card" data-title="高级配置 (JSON)">
<p class="theme-tip">
用于暂未在 UI 中开放的配置项(如复杂导航结构 header_nav/footer_nav 等)。如非必要,建议优先使用上方表单编辑。
</p>
<t-textarea v-model="advancedText" :autosize="{ minRows: 14 }" class="theme-textarea"></t-textarea>
<div class="mt-10">
<t-button variant="outline" @click="syncJson">同步当前配置</t-button>
<t-button class="ml-10" @click="applyAdvanced">应用 JSON</t-button>
</div>
</div>
</section>
</t-tabs>
<div class="action-bar">
<t-button variant="outline" @click="loadConfig" :loading="loading">重新加载</t-button>
<t-button theme="primary" class="ml-10" @click="saveConfig" :loading="saving">
保存全部配置
</t-button>
<t-button class="ml-10" variant="outline" @click="toggleAdvanced">
{{ showAdvanced ? "隐藏高级 JSON" : "显示高级 JSON" }}
</t-button>
</div>
</div>
<script src="/plugins/addon/theme_configurator/template/admin/lang/index.js"></script>
<script src="/plugins/addon/theme_configurator/template/admin/js/axios.min.js"></script>
<script>
(function () {
const host = location.origin;
const adminPath = location.pathname.split("/")[1];
const base = `${host}/${adminPath}/v1/theme/config`;
const showMessage = (vm, type, text) => {
if (vm.$message && typeof vm.$message[type] === "function") {
vm.$message[type](text);
} else if (window.MessagePlugin && typeof window.MessagePlugin[type] === "function") {
window.MessagePlugin[type](text);
} else {
alert(text);
}
};
const createDefaultConfig = () => ({
seo: {
title: "",
keywords: "",
description: "",
},
site_config: {
enterprise_name: "",
enterprise_telephone: "",
enterprise_mailbox: "",
enterprise_qrcode: "",
official_website_logo: "",
online_customer_service_link: "",
icp_info: "",
icp_info_link: "",
public_security_network_preparation: "",
public_security_network_preparation_link: "",
telecom_appreciation: "",
copyright_info: "",
terms_service_url: "",
terms_privacy_url: "",
cloud_product_link: "",
dcim_product_link: "",
},
banner: [],
header_nav: [],
footer_nav: [],
friendly_link: [],
side: [],
feedback_type: [],
honor: [],
partner: [],
});
new Vue({
el: "#theme-config-app",
data() {
return {
loading: false,
saving: false,
fullConfig: createDefaultConfig(),
advancedText: "",
showAdvanced: false,
activeTab: "basic",
uploadUrl: `${host}/${adminPath}/v1/upload`,
uploadHeaders: {
Authorization: "Bearer " + localStorage.getItem("backJwt"),
},
};
},
computed: {
bannerList() {
if (!Array.isArray(this.fullConfig.banner)) {
this.fullConfig.banner = [];
}
return this.fullConfig.banner;
},
headerNavList() {
if (!Array.isArray(this.fullConfig.header_nav)) {
this.fullConfig.header_nav = [];
}
if (this.fullConfig.header_nav.length === 0) {
this.fullConfig.header_nav.push({
name: "首页",
file_address: "index.html",
blank: false,
children: [],
});
}
return this.fullConfig.header_nav;
},
homeNav() {
return this.headerNavList[0];
},
footerNavList() {
if (!Array.isArray(this.fullConfig.footer_nav)) {
this.fullConfig.footer_nav = [];
}
return this.fullConfig.footer_nav;
},
},
methods: {
normalizeConfig(data = {}) {
const defaults = createDefaultConfig();
const merged = {
...defaults,
...data,
seo: {
...defaults.seo,
...(data.seo || {}),
},
site_config: {
...defaults.site_config,
...(data.site_config || {}),
},
banner: Array.isArray(data.banner) ? data.banner : [],
header_nav:
Array.isArray(data.header_nav) && data.header_nav.length
? data.header_nav
: [],
footer_nav: Array.isArray(data.footer_nav) ? data.footer_nav : [],
friendly_link: Array.isArray(data.friendly_link) ? data.friendly_link : [],
side: Array.isArray(data.side)
? data.side
: Array.isArray(data.side_floating_window)
? data.side_floating_window
: [],
feedback_type: Array.isArray(data.feedback_type)
? data.feedback_type
: Array.isArray(data.site_config && data.site_config.feedback_type)
? data.site_config.feedback_type
: [],
honor: Array.isArray(data.honor)
? data.honor
: Array.isArray(data.site_config && data.site_config.honor)
? data.site_config.honor
: [],
partner: Array.isArray(data.partner)
? data.partner
: Array.isArray(data.site_config && data.site_config.partner)
? data.site_config.partner
: [],
};
if (!Array.isArray(merged.header_nav) || merged.header_nav.length === 0) {
merged.header_nav = [
{
name: "首页",
file_address: "index.html",
blank: false,
children: [],
},
];
}
return merged;
},
uploadFormatResponse(res) {
if (!res || res.status !== 200) {
return { error: "上传失败" };
}
return res;
},
handleUpload(path, ctx) {
try {
const resp = ctx && ctx.file && ctx.file.response;
const data = resp && resp.data ? resp.data : resp;
if (!data) {
return showMessage(this, "error", "上传失败:未获取到响应数据");
}
// 优先使用后端返回的 image_url其次 url最后才是 save_name
let url = data.image_url || data.url || data.save_name || "";
if (!url) {
return showMessage(this, "error", "上传失败:未获取到文件地址");
}
// 相对路径前面补一个 /,保持站点根路径
if (!/^https?:\/\//i.test(url) && url.charAt(0) !== "/") {
url = "/" + url;
}
this.setConfigByPath(path, url);
} catch (e) {
showMessage(this, "error", "上传处理异常:" + e.message);
}
},
setConfigByPath(path, value) {
if (!Array.isArray(path) || !path.length) return;
let target = this.fullConfig;
for (let i = 0; i < path.length - 1; i++) {
const key = path[i];
if (typeof key === "number") {
if (!Array.isArray(target)) return;
target = target[key];
} else {
if (!target[key]) {
this.$set(target, key, {});
}
target = target[key];
}
if (!target) return;
}
const lastKey = path[path.length - 1];
if (typeof lastKey === "number") {
if (!Array.isArray(target)) return;
this.$set(target, lastKey, value);
} else {
this.$set(target, lastKey, value);
}
},
loadConfig() {
this.loading = true;
axios
.get(base, {
headers: {
Authorization: "Bearer " + localStorage.getItem("backJwt"),
},
})
.then((res) => {
const data = (res.data && res.data.data) || {};
this.fullConfig = this.normalizeConfig(data);
this.syncJson();
})
.finally(() => {
this.loading = false;
});
},
syncJson() {
this.advancedText = JSON.stringify(this.fullConfig, null, 2);
},
applyAdvanced() {
if (!this.advancedText.trim()) {
return showMessage(this, "warning", "JSON 内容为空");
}
try {
const parsed = JSON.parse(this.advancedText);
this.fullConfig = this.normalizeConfig(parsed);
showMessage(this, "success", "JSON 已应用");
} catch (err) {
showMessage(this, "error", "JSON 解析失败:" + err.message);
}
},
addBanner() {
this.bannerList.push({
title: "",
img: "",
description: "",
tags: "",
url: "",
blank: false,
button_text: "",
button_link: "",
button_blank: false,
});
},
removeBanner(index) {
this.bannerList.splice(index, 1);
},
addFriendlyLink() {
if (!Array.isArray(this.fullConfig.friendly_link)) {
this.fullConfig.friendly_link = [];
}
this.fullConfig.friendly_link.push({
name: "",
url: "",
});
},
removeFriendlyLink(index) {
this.fullConfig.friendly_link.splice(index, 1);
},
addHonor() {
if (!Array.isArray(this.fullConfig.honor)) {
this.fullConfig.honor = [];
}
this.fullConfig.honor.push({
name: "",
img: ""
});
},
removeHonor(index) {
this.fullConfig.honor.splice(index, 1);
},
addFeedbackType() {
if (!Array.isArray(this.fullConfig.feedback_type)) {
this.fullConfig.feedback_type = [];
}
this.fullConfig.feedback_type.push({
id: "",
name: "",
description: "",
});
},
removeFeedbackType(index) {
this.fullConfig.feedback_type.splice(index, 1);
},
addSide() {
if (!Array.isArray(this.fullConfig.side)) {
this.fullConfig.side = [];
}
this.fullConfig.side.push({
name: "",
icon: "",
content: "",
});
},
removeSide(index) {
this.fullConfig.side.splice(index, 1);
},
getHeaderChildren(item) {
if (!item) return [];
if (!Array.isArray(item.children)) {
this.$set(item, "children", []);
}
return item.children;
},
addHeaderNav() {
if (!Array.isArray(this.fullConfig.header_nav)) {
this.fullConfig.header_nav = [];
}
this.fullConfig.header_nav.push({
name: "",
file_address: "",
blank: false,
children: [],
});
},
removeHeaderNav(index) {
if (index === 0) {
return;
}
this.headerNavList.splice(index, 1);
},
addHeaderNavChild(index) {
const item = this.headerNavList[index];
if (!item) return;
if (!Array.isArray(item.children)) {
this.$set(item, "children", []);
}
item.children.push({
name: "",
file_address: "",
blank: false,
icon: "",
description: "",
});
},
removeHeaderNavChild(index, childIndex) {
const item = this.headerNavList[index];
if (!item || !Array.isArray(item.children)) return;
item.children.splice(childIndex, 1);
},
getFooterChildren(group) {
if (!group) return [];
if (!Array.isArray(group.children)) {
this.$set(group, "children", []);
}
return group.children;
},
addFooterNav() {
if (!Array.isArray(this.fullConfig.footer_nav)) {
this.fullConfig.footer_nav = [];
}
this.fullConfig.footer_nav.push({
name: "",
children: [],
});
},
removeFooterNav(index) {
this.footerNavList.splice(index, 1);
},
addFooterNavChild(index) {
const group = this.footerNavList[index];
if (!group) return;
if (!Array.isArray(group.children)) {
this.$set(group, "children", []);
}
group.children.push({
name: "",
url: "",
blank: false,
});
},
removeFooterNavChild(index, childIndex) {
const group = this.footerNavList[index];
if (!group || !Array.isArray(group.children)) return;
group.children.splice(childIndex, 1);
},
saveConfig() {
this.saving = true;
const payload = {
...this.fullConfig,
side_floating_window: this.fullConfig.side || [],
};
axios
.post(base, payload, {
headers: {
Authorization: "Bearer " + localStorage.getItem("backJwt"),
},
})
.then((res) => {
showMessage(this, "success", (res.data && res.data.msg) || "保存成功");
this.loadConfig();
})
.finally(() => {
this.saving = false;
});
},
toggleAdvanced() {
this.showAdvanced = !this.showAdvanced;
if (this.showAdvanced) {
this.syncJson();
this.activeTab = "advanced";
} else if (this.activeTab === "advanced") {
this.activeTab = "basic";
}
},
},
mounted() {
this.loadConfig();
},
});
})();
</script>