Files
BlackFruit-UI/plugins/addon/theme_configurator/template/admin/index.html
yiqiu 91b0d4f1e4
All checks were successful
continuous-integration/drone/push Build is passing
插件优化
2025-12-28 14:48:25 +08:00

1064 lines
41 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"></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">应用</button>
<button class="btn btn-secondary" id="copyJsonBtn">复制</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() {
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');
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');
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');
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');
container.innerHTML = '';
navs.forEach((nav, index) => {
const item = document.createElement('div');
item.className = 'config-item';
const hasChildren = Array.isArray(nav.children) && nav.children.length > 0;
item.innerHTML = `
<div class="config-item__header">
<h4>导航 ${index + 1}: ${nav.name || '(未命名)'}</h4>
<div style="display: flex; gap: 4px;">
<button class="btn-icon" onclick="toggleHeaderNavChildren(${index})" title="子菜单">☰${hasChildren ? ` (${nav.children.length})` : ''}</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-header-nav="${index}" data-field="name" value="${nav.name || ''}" placeholder="产品中心">
</div>
<div class="form-item">
<label>链接</label>
<input type="text" class="form-control" data-header-nav="${index}" data-field="file_address" value="${nav.file_address || ''}" placeholder="/products.html">
</div>
<div class="form-item">
<div class="form-switch">
<label>新窗口</label>
<input type="checkbox" data-header-nav="${index}" data-field="blank" ${nav.blank ? 'checked' : ''}>
</div>
</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(parentIndex, children) {
const container = document.getElementById(`header-nav-children-list-${parentIndex}`);
container.innerHTML = '';
children.forEach((child, childIndex) => {
const item = document.createElement('div');
item.className = 'config-item';
item.style.marginLeft = '20px';
item.innerHTML = `
<div class="config-item__header">
<h4 style="font-size:13px;">子项 ${childIndex + 1}</h4>
<button class="btn-icon btn-icon-danger" onclick="removeHeaderNavChild(${parentIndex},${childIndex})">×</button>
</div>
<div class="config-item__body">
<div class="form-fields">
<div class="form-item">
<label>名称</label>
<input type="text" class="form-control" data-header-nav-child="${parentIndex}.${childIndex}" data-field="name" value="${child.name || ''}" placeholder="轻量云">
</div>
<div class="form-item">
<label>链接</label>
<input type="text" class="form-control" data-header-nav-child="${parentIndex}.${childIndex}" data-field="file_address" value="${child.file_address || ''}" placeholder="/light.html">
</div>
<div class="form-item">
<label>图标URL</label>
<input type="text" class="form-control" data-header-nav-child="${parentIndex}.${childIndex}" data-field="icon" value="${child.icon || ''}" placeholder="/upload/icon.png">
</div>
<div class="form-item">
<div class="form-switch">
<label>新窗口</label>
<input type="checkbox" data-header-nav-child="${parentIndex}.${childIndex}" data-field="blank" ${child.blank ? 'checked' : ''}>
</div>
</div>
</div>
</div>
`;
container.appendChild(item);
});
}
function collectHeaderNav() {
const navs = [];
document.querySelectorAll('[data-header-nav]').forEach(input => {
const index = parseInt(input.dataset.headerNav);
const field = input.dataset.field;
if (!navs[index]) navs[index] = { children: [] };
navs[index][field] = input.type === 'checkbox' ? input.checked : input.value;
});
document.querySelectorAll('[data-header-nav-child]').forEach(input => {
const [parentIndex, childIndex] = input.dataset.headerNavChild.split('.').map(Number);
const field = input.dataset.field;
if (!navs[parentIndex].children[childIndex]) navs[parentIndex].children[childIndex] = {};
navs[parentIndex].children[childIndex][field] = input.type === 'checkbox' ? input.checked : input.value;
});
return navs.filter(n => n);
}
window.addHeaderNav = function() {
const navs = collectHeaderNav();
navs.push({ name: '', file_address: '', blank: false, children: [] });
renderHeaderNav(navs);
};
window.removeHeaderNav = function(index) {
if(confirm('确定删除此导航项吗?')) {
const navs = collectHeaderNav();
navs.splice(index, 1);
renderHeaderNav(navs);
}
};
window.toggleHeaderNavChildren = function(index) {
const container = document.getElementById(`header-nav-children-${index}`);
container.style.display = container.style.display === 'none' ? 'block' : 'none';
};
window.addHeaderNavChild = function(parentIndex) {
const navs = collectHeaderNav();
if (!navs[parentIndex].children) navs[parentIndex].children = [];
navs[parentIndex].children.push({ name: '', file_address: '', blank: false, icon: '', description: '' });
renderHeaderNav(navs);
document.getElementById(`header-nav-children-${parentIndex}`).style.display = 'block';
};
window.removeHeaderNavChild = function(parentIndex, childIndex) {
if(confirm('确定删除此子菜单吗?')) {
const navs = collectHeaderNav();
navs[parentIndex].children.splice(childIndex, 1);
renderHeaderNav(navs);
}
};
// ========== 底部导航 ==========
function renderFooterNav(navs) {
const container = document.getElementById('footerNavList');
container.innerHTML = '';
navs.forEach((col, colIndex) => {
const item = document.createElement('div');
item.className = 'config-item';
const hasChildren = Array.isArray(col.children) && col.children.length > 0;
item.innerHTML = `
<div class="config-item__header">
<h4>栏目 ${colIndex + 1}: ${col.name || '(未命名)'}</h4>
<div style="display: flex; gap: 4px;">
<button class="btn-icon" onclick="toggleFooterNavChildren(${colIndex})" title="链接列表">☰${hasChildren ? ` (${col.children.length})` : ''}</button>
<button class="btn-icon btn-icon-danger" onclick="removeFooterNav(${colIndex})">×</button>
</div>
</div>
<div class="config-item__body">
<div class="form-item">
<label>栏目名称</label>
<input type="text" class="form-control" data-footer-nav="${colIndex}" data-field="name" value="${col.name || ''}" placeholder="热门云产品">
</div>
<div id="footer-nav-children-${colIndex}" 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="footer-nav-children-list-${colIndex}"></div>
<button class="btn btn-secondary btn-sm" onclick="addFooterNavChild(${colIndex})" style="margin-top:8px;">+ 添加链接</button>
</div>
</div>
`;
container.appendChild(item);
if (hasChildren) {
renderFooterNavChildren(colIndex, col.children);
}
});
}
function renderFooterNavChildren(colIndex, children) {
const container = document.getElementById(`footer-nav-children-list-${colIndex}`);
container.innerHTML = '';
children.forEach((link, linkIndex) => {
const item = document.createElement('div');
item.className = 'config-item';
item.style.marginLeft = '20px';
item.innerHTML = `
<div class="config-item__header">
<h4 style="font-size:13px;">链接 ${linkIndex + 1}</h4>
<button class="btn-icon btn-icon-danger" onclick="removeFooterNavChild(${colIndex},${linkIndex})">×</button>
</div>
<div class="config-item__body">
<div class="form-fields">
<div class="form-item">
<label>名称</label>
<input type="text" class="form-control" data-footer-nav-child="${colIndex}.${linkIndex}" data-field="name" value="${link.name || ''}" placeholder="云服务器">
</div>
<div class="form-item">
<label>链接</label>
<input type="text" class="form-control" data-footer-nav-child="${colIndex}.${linkIndex}" data-field="url" value="${link.url || ''}" placeholder="/products/ecs.html">
</div>
<div class="form-item">
<div class="form-switch">
<label>新窗口</label>
<input type="checkbox" data-footer-nav-child="${colIndex}.${linkIndex}" data-field="blank" ${link.blank ? 'checked' : ''}>
</div>
</div>
</div>
</div>
`;
container.appendChild(item);
});
}
function collectFooterNav() {
const navs = [];
document.querySelectorAll('[data-footer-nav]').forEach(input => {
const index = parseInt(input.dataset.footerNav);
if (!navs[index]) navs[index] = { children: [] };
navs[index][input.dataset.field] = input.value;
});
document.querySelectorAll('[data-footer-nav-child]').forEach(input => {
const [colIndex, linkIndex] = input.dataset.footerNavChild.split('.').map(Number);
const field = input.dataset.field;
if (!navs[colIndex].children[linkIndex]) navs[colIndex].children[linkIndex] = {};
navs[colIndex].children[linkIndex][field] = input.type === 'checkbox' ? input.checked : input.value;
});
return navs.filter(n => n);
}
window.addFooterNav = function() {
const navs = collectFooterNav();
navs.push({ name: '', children: [] });
renderFooterNav(navs);
};
window.removeFooterNav = function(index) {
if(confirm('确定删除此栏目吗?')) {
const navs = collectFooterNav();
navs.splice(index, 1);
renderFooterNav(navs);
}
};
window.toggleFooterNavChildren = function(index) {
const container = document.getElementById(`footer-nav-children-${index}`);
container.style.display = container.style.display === 'none' ? 'block' : 'none';
};
window.addFooterNavChild = function(colIndex) {
const navs = collectFooterNav();
if (!navs[colIndex].children) navs[colIndex].children = [];
navs[colIndex].children.push({ name: '', url: '', blank: false });
renderFooterNav(navs);
document.getElementById(`footer-nav-children-${colIndex}`).style.display = 'block';
};
window.removeFooterNavChild = function(colIndex, linkIndex) {
if(confirm('确定删除此链接吗?')) {
const navs = collectFooterNav();
navs[colIndex].children.splice(linkIndex, 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();
axios.post(apiBase, data, {
headers: { Authorization: `Bearer ${token}` }
}).then(res => {
alert(res.data.msg || '保存成功');
loadConfig(); // 重新加载
}).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>