Files
BlackFruit-UI/plugins/addon/theme_configurator/template/admin/index.html
yiqiu 2846561d8c
All checks were successful
continuous-integration/drone/push Build is passing
子菜单优化UI
2025-12-28 22:01:28 +08:00

1013 lines
38 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">
<!-- 顶部工具栏 -->
<header class="admin-header">
<div class="admin-logo">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
<path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
<path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
<span>黑果云模板控制器</span>
</div>
<button class="btn btn-primary btn-lg" id="saveBtn">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M13.5 2.5H2.5V13.5H13.5V2.5Z" stroke="currentColor" stroke-width="1.5" />
<path d="M10.5 2.5V6.5H5.5V2.5" stroke="currentColor" stroke-width="1.5" />
<path d="M5.5 9.5H10.5V13.5H5.5V9.5Z" stroke="currentColor" stroke-width="1.5" />
</svg>
<span>保存全部配置</span>
</button>
</header>
<!-- Tab导航 -->
<nav class="admin-tabs">
<a class="tab-item active" data-tab="basic">
<svg viewBox="0 0 24 24" fill="none">
<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2" />
<line x1="3" y1="9" x2="21" y2="9" stroke="currentColor" stroke-width="2" />
</svg>
基础配置
</a>
<a class="tab-item" data-tab="seo">
<svg viewBox="0 0 24 24" fill="none">
<circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2" />
<path d="M21 21L16.65 16.65" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</svg>
SEO设置
</a>
<a class="tab-item" data-tab="home">
<svg viewBox="0 0 24 24" fill="none">
<path
d="M3 9L12 2L21 9V20C21 20.5304 20.7893 21.0391 20.4142 21.4142C20.0391 21.7893 19.5304 22 19 22H5C4.46957 22 3.96086 21.7893 3.58579 21.4142C3.21071 21.0391 3 20.5304 3 20V9Z"
stroke="currentColor" stroke-width="2" />
</svg>
首页内容
</a>
<a class="tab-item" data-tab="nav">
<svg viewBox="0 0 24 24" fill="none">
<line x1="3" y1="12" x2="21" y2="12" stroke="currentColor" stroke-width="2" />
<line x1="3" y1="6" x2="21" y2="6" stroke="currentColor" stroke-width="2" />
<line x1="3" y1="18" x2="21" y2="18" stroke="currentColor" stroke-width="2" />
</svg>
导航配置
</a>
<a class="tab-item" data-tab="other">
<svg viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" />
<circle cx="12" cy="5" r="1" fill="currentColor" />
<circle cx="12" cy="19" r="1" fill="currentColor" />
<circle cx="5" cy="12" r="1" fill="currentColor" />
<circle cx="19" cy="12" r="1" fill="currentColor" />
</svg>
其他配置
</a>
<a class="tab-item" data-tab="json">
<svg viewBox="0 0 24 24" fill="none">
<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2" />
<path d="M8 8L12 12L8 16" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<path d="M12 8L16 12L12 16" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</svg>
JSON编辑器
</a>
</nav>
<!-- 主内容区 -->
<main class="admin-main">
<!-- 基础配置 -->
<section id="tab-basic" class="config-section active">
<div class="section-card">
<div class="section-header">
<h2>企业信息</h2>
<p class="section-desc">配置企业基础联系信息</p>
</div>
<div class="section-body">
<div class="form-fields">
<div class="form-item">
<label>企业名称</label>
<input type="text" class="form-control" name="site_config.enterprise_name" placeholder="主题云">
</div>
<div class="form-item">
<label>联系电话</label>
<input type="text" class="form-control" name="site_config.enterprise_telephone"
placeholder="400-000-0000">
</div>
<div class="form-item">
<label>联系邮箱</label>
<input type="text" class="form-control" name="site_config.enterprise_mailbox"
placeholder="support@example.com">
</div>
<div class="form-item">
<label>网站Logo</label>
<div class="upload-control">
<input type="text" class="form-control" name="site_config.official_website_logo"
placeholder="/upload/logo.png">
<button class="btn btn-secondary upload-btn" data-target="site_config.official_website_logo">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
<path
d="M14 10V12.6667C14 13.0203 13.8595 13.3594 13.6095 13.6095C13.3594 13.8595 13.0203 14 12.6667 14H3.33333C2.97971 14 2.64057 13.8595 2.39052 13.6095C2.14048 13.3594 2 13.0203 2 12.6667V10"
stroke="currentColor" stroke-width="1.5" />
<path d="M11.3333 5.33333L8 2L4.66667 5.33333" stroke="currentColor" stroke-width="1.5" />
<path d="M8 2V10" stroke="currentColor" stroke-width="1.5" />
</svg>
选择文件
</button>
</div>
<div class="form-hint">建议尺寸: 200×60 像素</div>
</div>
<div class="form-item">
<label>ICP备案号</label>
<input type="text" class="form-control" name="site_config.icp_info" placeholder="京ICP备XXXX号">
</div>
<div class="form-item">
<label>版权信息</label>
<input type="text" class="form-control" name="site_config.copyright_info" placeholder="© 2025 主题云">
</div>
</div>
</div>
</div>
</section>
<!-- SEO设置 -->
<section id="tab-seo" class="config-section">
<div class="section-card">
<div class="section-header">
<h2>SEO设置</h2>
</div>
<div class="section-body">
<div class="form-fields">
<div class="form-item">
<label>站点标题</label>
<input type="text" class="form-control" name="seo.title" placeholder="首页标题">
</div>
<div class="form-item">
<label>关键词</label>
<input type="text" class="form-control" name="seo.keywords" placeholder="关键词,逗号分隔">
</div>
<div class="form-item">
<label>描述</label>
<textarea class="form-control" name="seo.description" rows="3" placeholder="站点描述"></textarea>
</div>
</div>
</div>
</div>
</section>
<!-- 首页内容 -->
<section id="tab-home" class="config-section">
<div class="section-card">
<div class="section-header">
<h2>首页轮播</h2>
</div>
<div class="section-body">
<div id="bannerList"></div>
<button class="btn btn-secondary" id="addBannerBtn">+ 添加轮播</button>
</div>
</div>
<div class="section-card">
<div class="section-header">
<h2>企业荣誉</h2>
</div>
<div class="section-body">
<div id="honorList"></div>
<button class="btn btn-secondary" id="addHonorBtn">+ 添加荣誉</button>
</div>
</div>
</section>
<!-- 导航配置 -->
<section id="tab-nav" class="config-section">
<div class="section-card">
<div class="section-header">
<h2>顶部导航</h2>
</div>
<div class="section-body">
<div id="headerNavList"></div>
<button class="btn btn-secondary" id="addHeaderNavBtn">+ 添加导航</button>
</div>
</div>
<div class="section-card">
<div class="section-header">
<h2>底部导航</h2>
</div>
<div class="section-body">
<div id="footerNavList"></div>
<button class="btn btn-secondary" id="addFooterNavBtn">+ 添加栏目</button>
</div>
</div>
</section>
<!-- 其他配置 -->
<section id="tab-other" class="config-section">
<div class="section-card">
<div class="section-header">
<h2>友情链接</h2>
</div>
<div class="section-body">
<div id="friendlyLinkList"></div>
<button class="btn btn-secondary" id="addFriendlyLinkBtn">+ 添加</button>
</div>
</div>
<div class="section-card">
<div class="section-header">
<h2>侧边浮窗</h2>
</div>
<div class="section-body">
<div id="sideList"></div>
<button class="btn btn-secondary" id="addSideBtn">+ 添加</button>
</div>
</div>
<div class="section-card">
<div class="section-header">
<h2>反馈类型</h2>
</div>
<div class="section-body">
<div id="feedbackTypeList"></div>
<button class="btn btn-secondary" id="addFeedbackTypeBtn">+ 添加</button>
</div>
</div>
</section>
<!-- JSON编辑器 -->
<section id="tab-json" class="config-section">
<div class="section-card">
<div class="section-header">
<h2>JSON配置编辑器</h2>
<p class="section-desc">直接编辑完整JSON配置</p>
</div>
<div class="section-body">
<div class="alert alert-info" style="margin-bottom: 16px;">
<strong>使用说明:</strong> 点击"同步"按钮将表单数据转为JSON,编辑后点击"应用"更新表单,最后点击页面顶部"保存全部配置"
</div>
<textarea id="jsonEditor" class="json-editor" rows="20" placeholder='点击"同步配置"按钮加载当前数据...'></textarea>
<div style="margin-top: 16px; display: flex; gap: 12px;">
<button class="btn btn-secondary" id="syncJsonBtn">同步配置</button>
<button class="btn btn-primary" id="applyJsonBtn">应用JSON</button>
<button class="btn btn-secondary" id="copyJsonBtn">复制JSON</button>
</div>
</div>
</div>
</section>
</main>
</div>
<!-- 隐藏的文件上传input -->
<input type="file" id="fileInput" accept="image/*" style="display:none">
<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 apiBase = `${host}/${adminPath}/v1/theme/config`;
const uploadUrl = `${host}/${adminPath}/v1/upload`;
const token = localStorage.getItem("backJwt");
let config = {};
let currentUploadTarget = null;
// Tab切换
document.querySelectorAll('.tab-item').forEach(tab => {
tab.addEventListener('click', (e) => {
e.preventDefault();
const tabName = tab.dataset.tab;
// 更新Tab active状态
document.querySelectorAll('.tab-item').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// 更新section显示
document.querySelectorAll('.config-section').forEach(s => s.classList.remove('active'));
document.getElementById(`tab-${tabName}`).classList.add('active');
});
});
// 加载配置
function loadConfig() {
console.log('开始加载配置...');
axios.get(apiBase, {
headers: { Authorization: `Bearer ${token}` }
}).then(res => {
config = res.data.data || {};
fillForm(config);
}).catch(err => {
alert('加载配置失败: ' + (err.message || '未知错误'));
});
}
// 填充表单
function fillForm(data) {
// 填充简单字段
document.querySelectorAll('input[name], textarea[name]').forEach(input => {
const name = input.getAttribute('name');
const value = getNestedValue(data, name);
if (value !== undefined) {
input.value = value;
}
});
// 渲染轮播列表
renderBanners(data.banner || []);
// 渲染荣誉列表
renderHonors(data.honor || []);
// 渲染导航
renderHeaderNav(data.header_nav || []);
renderFooterNav(data.footer_nav || []);
// 渲染其他配置
renderFriendlyLinks(data.friendly_link || []);
renderSides(data.side || []);
renderFeedbackTypes(data.feedback_type || []);
}
// 获取嵌套属性值
function getNestedValue(obj, path) {
return path.split('.').reduce((val, key) => val?.[key], obj);
}
// 设置嵌套属性值
function setNestedValue(obj, path, value) {
const keys = path.split('.');
const last = keys.pop();
const target = keys.reduce((o, k) => o[k] = o[k] || {}, obj);
target[last] = value;
}
// 收集表单数据
function collectFormData() {
const data = JSON.parse(JSON.stringify(config)); // 深拷贝
document.querySelectorAll('input[name], textarea[name]').forEach(input => {
const name = input.getAttribute('name');
setNestedValue(data, name, input.value);
});
// 收集轮播数据
data.banner = collectBanners();
// 收集荣誉数据
data.honor = collectHonors();
data.friendly_link = collectFriendlyLinks();
data.side = collectSides();
data.feedback_type = collectFeedbackTypes();
data.header_nav = collectHeaderNav();
data.footer_nav = collectFooterNav();
return data;
}
// 渲染轮播列表
function renderBanners(banners) {
const container = document.getElementById('bannerList');
container.innerHTML = '';
banners.forEach((banner, index) => {
const item = document.createElement('div');
item.className = 'config-item';
item.innerHTML = `
<div class="config-item__header">
<h4>轮播 ${index + 1}</h4>
<button class="btn-icon btn-icon-danger" onclick="removeBanner(${index})">×</button>
</div>
<div class="config-item__body">
<div class="form-fields">
<div class="form-item">
<label>标题</label>
<input type="text" class="form-control" data-banner="${index}" data-field="title" value="${banner.title || ''}" placeholder="弹性算力">
</div>
<div class="form-item">
<label>描述</label>
<input type="text" class="form-control" data-banner="${index}" data-field="description" value="${banner.description || ''}" placeholder="宣传语">
</div>
<div class="form-item">
<label>图片地址</label>
<div class="upload-control">
<input type="text" class="form-control" data-banner="${index}" data-field="img" value="${banner.img || ''}" placeholder="/upload/banner.png">
<button class="btn btn-secondary upload-btn" data-target-banner="${index}.img">选择文件</button>
</div>
</div>
</div>
</div>
`;
container.appendChild(item);
});
}
// 收集轮播数据
function collectBanners() {
const banners = [];
document.querySelectorAll('[data-banner]').forEach(input => {
const index = parseInt(input.dataset.banner);
const field = input.dataset.field;
if (!banners[index]) {
banners[index] = {};
}
banners[index][field] = input.value;
});
return banners.filter(b => b); // 移除空项
}
// 添加轮播
window.addBanner = function () {
const banners = collectBanners();
banners.push({ title: '', description: '', img: '' });
renderBanners(banners);
};
// 删除轮播
window.removeBanner = function (index) {
const banners = collectBanners();
banners.splice(index, 1);
renderBanners(banners);
};
// 渲染荣誉列表
function renderHonors(honors) {
const container = document.getElementById('honorList');
container.innerHTML = '';
honors.forEach((honor, index) => {
const item = document.createElement('div');
item.className = 'config-item';
item.innerHTML = `
<div class="config-item__header">
<h4>荣誉 ${index + 1}</h4>
<button class="btn-icon btn-icon-danger" onclick="removeHonor(${index})">×</button>
</div>
<div class="config-item__body">
<div class="form-fields">
<div class="form-item">
<label>名称</label>
<input type="text" class="form-control" data-honor="${index}" data-field="name" value="${honor.name || ''}" placeholder="高新技术企业">
</div>
<div class="form-item">
<label>图片地址</label>
<div class="upload-control">
<input type="text" class="form-control" data-honor="${index}" data-field="img" value="${honor.img || ''}" placeholder="/upload/honor.png">
<button class="btn btn-secondary upload-btn" data-target-honor="${index}.img">选择文件</button>
</div>
</div>
</div>
</div>
`;
container.appendChild(item);
});
}
// 收集荣誉数据
function collectHonors() {
const honors = [];
document.querySelectorAll('[data-honor]').forEach(input => {
const index = parseInt(input.dataset.honor);
const field = input.dataset.field;
if (!honors[index]) {
honors[index] = {};
}
honors[index][field] = input.value;
});
return honors.filter(h => h);
}
// 添加荣誉
window.addHonor = function () {
const honors = collectHonors();
honors.push({ name: '', img: '' });
renderHonors(honors);
};
// 删除荣誉
window.removeHonor = function (index) {
const honors = collectHonors();
honors.splice(index, 1);
renderHonors(honors);
};
// ========== 友情链接 ==========
function renderFriendlyLinks(links) {
const container = document.getElementById('friendlyLinkList');
if (!container) return;
container.innerHTML = '';
links.forEach((link, index) => {
const item = document.createElement('div');
item.className = 'config-item';
item.innerHTML = `
<div class="config-item__header">
<h4>链接 ${index + 1}</h4>
<button class="btn-icon btn-icon-danger" onclick="removeFriendlyLink(${index})">×</button>
</div>
<div class="config-item__body">
<div class="form-fields">
<div class="form-item">
<label>名称</label>
<input type="text" class="form-control" data-friendly="${index}" data-field="name" value="${link.name || ''}" placeholder="网站名称">
</div>
<div class="form-item">
<label>链接地址</label>
<input type="text" class="form-control" data-friendly="${index}" data-field="url" value="${link.url || ''}" placeholder="https://example.com">
</div>
</div>
</div>
`;
container.appendChild(item);
});
}
function collectFriendlyLinks() {
const links = [];
document.querySelectorAll('[data-friendly]').forEach(input => {
const index = parseInt(input.dataset.friendly);
const field = input.dataset.field;
if (!links[index]) links[index] = {};
links[index][field] = input.value;
});
return links.filter(l => l);
}
window.addFriendlyLink = function () {
const links = collectFriendlyLinks();
links.push({ name: '', url: '' });
renderFriendlyLinks(links);
};
window.removeFriendlyLink = function (index) {
const links = collectFriendlyLinks();
links.splice(index, 1);
renderFriendlyLinks(links);
};
// ========== 侧边浮窗 ==========
function renderSides(sides) {
const container = document.getElementById('sideList');
if (!container) return;
container.innerHTML = '';
sides.forEach((side, index) => {
const item = document.createElement('div');
item.className = 'config-item';
item.innerHTML = `
<div class="config-item__header">
<h4>浮窗 ${index + 1}</h4>
<button class="btn-icon btn-icon-danger" onclick="removeSide(${index})">×</button>
</div>
<div class="config-item__body">
<div class="form-fields">
<div class="form-item">
<label>名称</label>
<input type="text" class="form-control" data-side="${index}" data-field="name" value="${side.name || ''}" placeholder="电话咨询">
</div>
<div class="form-item">
<label>图标地址</label>
<div class="upload-control">
<input type="text" class="form-control" data-side="${index}" data-field="icon" value="${side.icon || ''}" placeholder="/upload/icon.png">
<button class="btn btn-secondary upload-btn" data-target-side="${index}.icon">选择文件</button>
</div>
</div>
<div class="form-item">
<label>内容HTML</label>
<textarea class="form-control" data-side="${index}" data-field="content" rows="2" placeholder="HTML内容">${side.content || ''}</textarea>
</div>
</div>
</div>
`;
container.appendChild(item);
});
}
function collectSides() {
const sides = [];
document.querySelectorAll('[data-side]').forEach(input => {
const index = parseInt(input.dataset.side);
const field = input.dataset.field;
if (!sides[index]) sides[index] = {};
sides[index][field] = input.value;
});
return sides.filter(s => s);
}
window.addSide = function () {
const sides = collectSides();
sides.push({ name: '', icon: '', content: '' });
renderSides(sides);
};
window.removeSide = function (index) {
const sides = collectSides();
sides.splice(index, 1);
renderSides(sides);
};
// ========== 反馈类型 ==========
function renderFeedbackTypes(types) {
const container = document.getElementById('feedbackTypeList');
if (!container) return;
container.innerHTML = '';
types.forEach((type, index) => {
const item = document.createElement('div');
item.className = 'config-item';
item.innerHTML = `
<div class="config-item__header">
<h4>类型 ${index + 1}</h4>
<button class="btn-icon btn-icon-danger" onclick="removeFeedbackType(${index})">×</button>
</div>
<div class="config-item__body">
<div class="form-fields">
<div class="form-item">
<label>ID</label>
<input type="text" class="form-control" data-feedback="${index}" data-field="id" value="${type.id || ''}" placeholder="1">
</div>
<div class="form-item">
<label>名称</label>
<input type="text" class="form-control" data-feedback="${index}" data-field="name" value="${type.name || ''}" placeholder="产品建议">
</div>
<div class="form-item">
<label>描述</label>
<input type="text" class="form-control" data-feedback="${index}" data-field="description" value="${type.description || ''}" placeholder="用于产品体验反馈">
</div>
</div>
</div>
`;
container.appendChild(item);
});
}
function collectFeedbackTypes() {
const types = [];
document.querySelectorAll('[data-feedback]').forEach(input => {
const index = parseInt(input.dataset.feedback);
const field = input.dataset.field;
if (!types[index]) types[index] = {};
types[index][field] = input.value;
});
return types.filter(t => t);
}
window.addFeedbackType = function () {
const types = collectFeedbackTypes();
types.push({ id: '', name: '', description: '' });
renderFeedbackTypes(types);
};
window.removeFeedbackType = function (index) {
const types = collectFeedbackTypes();
types.splice(index, 1);
renderFeedbackTypes(types);
};
// ========== 顶部导航(完整版 - 支持子菜单) ==========
function renderHeaderNav(navs) {
const container = document.getElementById('headerNavList');
if (!container) return;
container.innerHTML = '';
navs.forEach((nav, index) => {
const hasChildren = Array.isArray(nav.children) && nav.children.length > 0;
const item = document.createElement('div');
item.className = 'config-item';
item.innerHTML = `
<div class="config-item__header">
<h4>导航 ${index + 1}: ${nav.name || '(未命名)'}</h4>
<div style="display: flex; gap: 4px;">
<button class="btn btn-secondary btn-sm" id="toggle-nav-${index}" onclick="toggleHeaderNavChildren(${index})" style="font-size:12px;">${hasChildren && nav.children.length > 0 ? '收起子菜单' : '展开子菜单'}</button>
<button class="btn-icon btn-icon-danger" onclick="removeHeaderNav(${index})">×</button>
</div>
</div>
<div class="config-item__body">
<div class="form-fields">
<div class="form-item">
<label>名称</label>
<input type="text" class="form-control" data-hnav="${index}" data-field="name" value="${nav.name || ''}" placeholder="产品中心">
</div>
<div class="form-item">
<label>链接</label>
<input type="text" class="form-control" data-hnav="${index}" data-field="file_address" value="${nav.file_address || ''}" placeholder="/products.html">
</div>
</div>
<div id="header-nav-children-${index}" style="display:${hasChildren ? 'block' : 'none'}; margin-top:12px; padding-top:12px; border-top:1px solid #eee;">
<h5 style="margin:0 0 8px; font-size:13px;">子菜单</h5>
<div id="header-nav-children-list-${index}"></div>
<button class="btn btn-secondary btn-sm" onclick="addHeaderNavChild(${index})" style="margin-top:8px;">+ 添加子菜单</button>
</div>
</div>
`;
container.appendChild(item);
if (hasChildren) {
renderHeaderNavChildren(index, nav.children);
}
});
}
function renderHeaderNavChildren(navIndex, children) {
const container = document.getElementById(`header-nav-children-list-${navIndex}`);
if (!container) return;
container.innerHTML = '';
children.forEach((child, childIndex) => {
const item = document.createElement('div');
item.style.cssText = 'padding:8px; margin-bottom:8px; background:#f9f9f9; border-radius:4px;';
item.innerHTML = `
<div style="display:flex; justify-content:space-between; align-items:start;">
<div style="flex:1; display:grid; gap:8px;">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="name" value="${child.name || ''}" placeholder="子菜单名称">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="file_address" value="${child.file_address || ''}" placeholder="链接地址">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="icon" value="${child.icon || ''}" placeholder="图标URL(可选)">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="description" value="${child.description || ''}" placeholder="描述(可选)">
<label style="font-size:12px;"><input type="checkbox" data-hnav-child="${navIndex}.${childIndex}" data-field="blank" ${child.blank ? 'checked' : ''}> 新窗口打开</label>
</div>
<button class="btn-icon btn-icon-danger" onclick="removeHeaderNavChild(${navIndex}, ${childIndex})" style="margin-left:8px;">×</button>
</div>
`;
container.appendChild(item);
});
}
function collectHeaderNav() {
const navs = [];
// 收集主导航
document.querySelectorAll('[data-hnav]').forEach(input => {
const index = parseInt(input.dataset.hnav);
const field = input.dataset.field;
if (!navs[index]) navs[index] = { children: [] };
navs[index][field] = input.value;
});
// 收集子菜单
document.querySelectorAll('[data-hnav-child]').forEach(input => {
const [navIndex, childIndex] = input.dataset.hnavChild.split('.').map(Number);
const field = input.dataset.field;
if (!navs[navIndex]) navs[navIndex] = { children: [] };
if (!navs[navIndex].children[childIndex]) {
navs[navIndex].children[childIndex] = {};
}
if (input.type === 'checkbox') {
navs[navIndex].children[childIndex][field] = input.checked;
} else {
navs[navIndex].children[childIndex][field] = input.value;
}
});
return navs.filter(n => n);
}
window.addHeaderNav = function () {
const navs = collectHeaderNav();
navs.push({ name: '', file_address: '', children: [] });
renderHeaderNav(navs);
};
window.removeHeaderNav = function (index) {
const navs = collectHeaderNav();
navs.splice(index, 1);
renderHeaderNav(navs);
};
window.toggleHeaderNavChildren = function (index) {
const container = document.getElementById(`header-nav-children-${index}`);
const toggleBtn = document.getElementById(`toggle-nav-${index}`);
if (container && toggleBtn) {
const isHidden = container.style.display === 'none';
container.style.display = isHidden ? 'block' : 'none';
toggleBtn.textContent = isHidden ? '收起子菜单' : '展开子菜单';
}
};
window.addHeaderNavChild = function (navIndex) {
const navs = collectHeaderNav();
if (!navs[navIndex]) navs[navIndex] = { children: [] };
if (!navs[navIndex].children) navs[navIndex].children = [];
navs[navIndex].children.push({
name: '',
file_address: '',
icon: '',
description: '',
blank: false
});
renderHeaderNav(navs);
// 确保展开
document.getElementById(`header-nav-children-${navIndex}`).style.display = 'block';
};
window.removeHeaderNavChild = function (navIndex, childIndex) {
const navs = collectHeaderNav();
if (navs[navIndex] && navs[navIndex].children) {
navs[navIndex].children.splice(childIndex, 1);
renderHeaderNav(navs);
}
};
// ========== 底部导航(简化) ==========
function renderFooterNav(navs) {
const container = document.getElementById('footerNavList');
if (!container) return;
container.innerHTML = '';
navs.forEach((col, index) => {
const item = document.createElement('div');
item.className = 'config-item';
item.innerHTML = `
<div class="config-item__header">
<h4>栏目 ${index + 1}</h4>
<button class="btn-icon btn-icon-danger" onclick="removeFooterNav(${index})">×</button>
</div>
<div class="config-item__body">
<div class="form-item">
<label>栏目名称</label>
<input type="text" class="form-control" data-fnav="${index}" data-field="name" value="${col.name || ''}" placeholder="热门云产品">
</div>
<div class="form-item">
<div style="font-size:13px;color:#666;">链接列表请使用JSON编辑器配置</div>
</div>
</div>
`;
container.appendChild(item);
});
}
function collectFooterNav() {
const navs = [];
document.querySelectorAll('[data-fnav]').forEach(input => {
const index = parseInt(input.dataset.fnav);
if (!navs[index]) navs[index] = { children: [] };
navs[index][input.dataset.field] = input.value;
});
// 保留原有的children
const orig = config.footer_nav || [];
navs.forEach((nav, i) => {
if (orig[i] && orig[i].children) {
nav.children = orig[i].children;
}
});
return navs.filter(n => n);
}
window.addFooterNav = function () {
const navs = collectFooterNav();
navs.push({ name: '', children: [] });
renderFooterNav(navs);
};
window.removeFooterNav = function (index) {
const navs = collectFooterNav();
navs.splice(index, 1);
renderFooterNav(navs);
};
// 文件上传
document.addEventListener('click', (e) => {
if (e.target.closest('.upload-btn')) {
e.preventDefault();
const btn = e.target.closest('.upload-btn');
currentUploadTarget = btn.dataset.target || btn.dataset.targetBanner || btn.dataset.targetHonor || btn.dataset.targetSide;
document.getElementById('fileInput').click();
}
});
document.getElementById('fileInput').addEventListener('change', function (e) {
const file = e.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
const saveBtn = document.getElementById('saveBtn');
saveBtn.disabled = true;
saveBtn.querySelector('span').textContent = '上传中...';
axios.post(uploadUrl, formData, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'multipart/form-data'
}
}).then(res => {
const data = res.data.data || {};
const url = data.image_url || data.url || data.save_name || '';
if (url) {
// 找到目标input并设置值
if (currentUploadTarget.includes('.')) {
// 动态列表项
const input = document.querySelector(`[data-target-banner="${currentUploadTarget}"], [data-target-honor="${currentUploadTarget}"]`)
?.closest('.upload-control')
?.querySelector('input');
if (input) input.value = url.startsWith('/') ? url : '/' + url;
} else {
// 普通字段
const input = document.querySelector(`input[name="${currentUploadTarget}"]`);
if (input) input.value = url.startsWith('/') ? url : '/' + url;
}
alert('上传成功');
}
}).catch(err => {
alert('上传失败: ' + (err.response?.data?.msg || err.message));
}).finally(() => {
saveBtn.disabled = false;
saveBtn.querySelector('span').textContent = '保存全部配置';
e.target.value = ''; // 清空文件选择
});
});
// 保存配置
document.getElementById('saveBtn').addEventListener('click', () => {
const btn = document.getElementById('saveBtn');
btn.disabled = true;
btn.querySelector('span').textContent = '保存中...';
const data = collectFormData();
console.log('保存的数据:', data);
console.log('保存的数据JSON:', JSON.stringify(data, null, 2)); // 调试日志
axios.post(apiBase, data, {
headers: { Authorization: `Bearer ${token}` }
}).then(res => {
console.log('服务器响应:', res.data);
alert(res.data.msg || '保存成功');
// 使用服务器返回的数据更新config
config = res.data.data || data;
// 清除前端缓存,确保下次访问时重新加载最新数据
sessionStorage.removeItem('commentData');
// 重新渲染界面
fillForm(config);
}).catch(err => {
alert('保存失败: ' + (err.response?.data?.msg || err.message));
}).finally(() => {
btn.disabled = false;
btn.querySelector('span').textContent = '保存全部配置';
});
});
// 添加按钮事件
document.getElementById('addBannerBtn').addEventListener('click', addBanner);
document.getElementById('addHonorBtn').addEventListener('click', addHonor);
document.getElementById('addFriendlyLinkBtn').addEventListener('click', addFriendlyLink);
document.getElementById('addSideBtn').addEventListener('click', addSide);
document.getElementById('addFeedbackTypeBtn').addEventListener('click', addFeedbackType);
document.getElementById('addHeaderNavBtn').addEventListener('click', addHeaderNav);
document.getElementById('addFooterNavBtn').addEventListener('click', addFooterNav);
// JSON编辑器功能
document.getElementById('syncJsonBtn').addEventListener('click', () => {
const data = collectFormData();
document.getElementById('jsonEditor').value = JSON.stringify(data, null, 2);
alert('✓ 已同步当前配置到JSON编辑器');
});
document.getElementById('applyJsonBtn').addEventListener('click', () => {
const jsonText = document.getElementById('jsonEditor').value.trim();
if (!jsonText) {
alert('⚠ JSON内容为空');
return;
}
try {
const data = JSON.parse(jsonText);
config = data;
fillForm(data);
alert('✓ JSON已应用到表单,请切换到其他Tab查看');
} catch (err) {
alert('✗ JSON解析失败:\n' + err.message);
}
});
document.getElementById('copyJsonBtn').addEventListener('click', () => {
const jsonText = document.getElementById('jsonEditor').value;
if (!jsonText) {
alert('⚠ JSON内容为空');
return;
}
navigator.clipboard.writeText(jsonText).then(() => {
alert('✓ JSON已复制到剪贴板');
}).catch(err => {
// 兼容方案
const textarea = document.createElement('textarea');
textarea.value = jsonText;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
alert('✓ JSON已复制到剪贴板');
});
});
// 初始化
loadConfig();
})();
</script>
</html>