1403 lines
57 KiB
HTML
1403 lines
57 KiB
HTML
<link rel="stylesheet" href="/plugins/addon/theme_configurator/template/admin/theme.css" />
|
||
<style>
|
||
/* 优化输入框和下拉框的focus样式 */
|
||
input[type="text"]:focus,
|
||
input[type="number"]:focus,
|
||
textarea:focus,
|
||
select:focus {
|
||
outline: none !important;
|
||
border-color: #1890ff !important;
|
||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2) !important;
|
||
}
|
||
|
||
/* 表格内的输入框样式优化 */
|
||
.nav-config-table input:focus,
|
||
.nav-config-table select:focus {
|
||
outline: none !important;
|
||
border-color: #1890ff !important;
|
||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.15) !important;
|
||
}
|
||
|
||
/* 移除所有输入框的默认outline */
|
||
input,
|
||
select,
|
||
textarea {
|
||
outline: none;
|
||
}
|
||
</style>
|
||
|
||
|
||
<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>Logo链接地址</label>
|
||
<input type="text" class="form-control" name="site_config.logo_link" placeholder="index.html">
|
||
<div class="form-hint">点击Logo跳转的链接地址,默认为首页</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>
|
||
<p class="section-desc">配置网站顶部导航栏菜单项,支持添加子菜单。注意:Logo链接已在"基础配置"中独立设置,所有导航项都会正常显示。</p>
|
||
</div>
|
||
<div class="section-body">
|
||
<div id="headerNavList"></div>
|
||
<!-- 添加导航按钮已整合到表格底部 -->
|
||
</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;
|
||
|
||
// 确保navs是数组
|
||
if (!Array.isArray(navs)) {
|
||
navs = [];
|
||
}
|
||
|
||
let html = `
|
||
<table class="nav-config-table" style="width:100%; border-collapse:collapse; font-size:12px;">
|
||
<thead>
|
||
<tr style="background:#f5f5f5;">
|
||
<th style="width:40px; padding:8px; border:1px solid #e0e0e0;">级</th>
|
||
<th style="width:180px; padding:8px; border:1px solid #e0e0e0;">名称</th>
|
||
<th style="width:180px; padding:8px; border:1px solid #e0e0e0;">链接</th>
|
||
<th style="width:140px; padding:8px; border:1px solid #e0e0e0;">图标/地区</th>
|
||
<th style="width:120px; padding:8px; border:1px solid #e0e0e0;">描述</th>
|
||
<th style="width:60px; padding:8px; border:1px solid #e0e0e0;">选项</th>
|
||
<th style="width:100px; padding:8px; border:1px solid #e0e0e0;">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="nav-tbody">
|
||
`;
|
||
|
||
navs.forEach((nav, navIndex) => {
|
||
// 一级导航行
|
||
html += `
|
||
<tr style="background:#fafafa; border-left:3px solid #52c41a;">
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<span style="display:inline-block; padding:2px 6px; background:#52c41a; color:#fff; border-radius:3px; font-size:10px;">1级</span>
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<input type="text" data-hnav="${navIndex}" data-field="name" value="${nav.name || ''}" placeholder="导航名称" style="width:100%; padding:4px; border:1px solid #d9d9d9; font-size:12px;">
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<input type="text" data-hnav="${navIndex}" data-field="file_address" value="${nav.file_address || ''}" placeholder="链接(可选)" style="width:100%; padding:4px; border:1px solid #d9d9d9; font-size:12px;">
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8; text-align:center;">-</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8; text-align:center;">-</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<label style="font-size:11px; margin:0;"><input type="checkbox" data-hnav="${navIndex}" data-field="blank" ${nav.blank ? 'checked' : ''}> 新窗口</label>
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<button onclick="toggleNavChildren(${navIndex})" style="padding:2px 8px; background:#52c41a; color:#fff; border:none; border-radius:2px; font-size:11px; cursor:pointer; margin-right:4px;">+子</button>
|
||
<button onclick="removeHeaderNav(${navIndex})" style="padding:2px 8px; background:#ff4d4f; color:#fff; border:none; border-radius:2px; font-size:11px; cursor:pointer;">删</button>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
|
||
// 二级菜单(默认隐藏)
|
||
if (nav.children && nav.children.length > 0) {
|
||
nav.children.forEach((child, childIndex) => {
|
||
html += `
|
||
<tr id="nav-child-${navIndex}-${childIndex}" style="display:none; background:#f0f7ff; border-left:3px solid #1890ff;">
|
||
<td style="padding:6px 8px 6px 32px; border:1px solid #e8e8e8;">
|
||
<span style="display:inline-block; padding:2px 6px; background:#1890ff; color:#fff; border-radius:3px; font-size:10px;">2级</span>
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<input type="text" data-hnav-child="${navIndex}.${childIndex}" data-field="name" value="${child.name || ''}" placeholder="二级名称" style="width:100%; padding:4px; border:1px solid #d9d9d9; font-size:12px;">
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<input type="text" data-hnav-child="${navIndex}.${childIndex}" data-field="file_address" value="${child.file_address || ''}" placeholder="链接(可选)" style="width:100%; padding:4px; border:1px solid #d9d9d9; font-size:12px;">
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<input type="text" data-hnav-child="${navIndex}.${childIndex}" data-field="icon" value="${child.icon || ''}" placeholder="图标URL" style="width:100%; padding:4px; border:1px solid #d9d9d9; font-size:11px;">
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<input type="text" data-hnav-child="${navIndex}.${childIndex}" data-field="description" value="${child.description || ''}" placeholder="描述" style="width:100%; padding:4px; border:1px solid #d9d9d9; font-size:11px;">
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<label style="font-size:11px; margin:0;"><input type="checkbox" data-hnav-child="${navIndex}.${childIndex}" data-field="blank" ${child.blank ? 'checked' : ''}> 新窗口</label>
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<button onclick="toggleThirdLevel(${navIndex}, ${childIndex})" style="padding:2px 8px; background:#1890ff; color:#fff; border:none; border-radius:2px; font-size:11px; cursor:pointer; margin-right:4px;">+三</button>
|
||
<button onclick="removeHeaderNavChild(${navIndex}, ${childIndex})" style="padding:2px 8px; background:#ff4d4f; color:#fff; border:none; border-radius:2px; font-size:11px; cursor:pointer;">删</button>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
|
||
// 三级菜单(默认隐藏)
|
||
if (child.children && child.children.length > 0) {
|
||
child.children.forEach((grandChild, grandIndex) => {
|
||
const countryOptions = getCountryOptions();
|
||
const selectedCountry = grandChild.country_code || '';
|
||
html += `
|
||
<tr id="nav-grandchild-${navIndex}-${childIndex}-${grandIndex}" style="display:none; background:#fff7e6; border-left:3px solid #fa8c16;">
|
||
<td style="padding:6px 8px 6px 56px; border:1px solid #e8e8e8;">
|
||
<span style="display:inline-block; padding:2px 6px; background:#fa8c16; color:#fff; border-radius:3px; font-size:10px;">3级</span>
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<input type="text" data-hnav-grandchild="${navIndex}.${childIndex}.${grandIndex}" data-field="name" value="${grandChild.name || ''}" placeholder="三级名称" style="width:100%; padding:4px; border:1px solid #d9d9d9; font-size:11px;">
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<input type="text" data-hnav-grandchild="${navIndex}.${childIndex}.${grandIndex}" data-field="file_address" value="${grandChild.file_address || ''}" placeholder="链接" style="width:100%; padding:4px; border:1px solid #d9d9d9; font-size:11px;">
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<select data-hnav-grandchild="${navIndex}.${childIndex}.${grandIndex}" data-field="country_code" style="width:100%; padding:4px; border:1px solid #d9d9d9; font-size:11px;">
|
||
<option value="">无地区</option>
|
||
${countryOptions.map(country => `<option value="${country.code}" ${selectedCountry === country.code ? 'selected' : ''}>${country.name}(${country.code})</option>`).join('')}
|
||
</select>
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<input type="text" data-hnav-grandchild="${navIndex}.${childIndex}.${grandIndex}" data-field="description" value="${grandChild.description || ''}" placeholder="描述" style="width:100%; padding:4px; border:1px solid #d9d9d9; font-size:11px;">
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<label style="font-size:11px; margin:0;"><input type="checkbox" data-hnav-grandchild="${navIndex}.${childIndex}.${grandIndex}" data-field="blank" ${grandChild.blank ? 'checked' : ''}> 新窗口</label>
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<button onclick="removeThirdLevelItem(${navIndex}, ${childIndex}, ${grandIndex})" style="padding:2px 8px; background:#ff4d4f; color:#fff; border:none; border-radius:2px; font-size:11px; cursor:pointer;">删</button>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
});
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
html += `
|
||
</tbody>
|
||
</table>
|
||
<button onclick="addHeaderNav()" style="margin-top:12px; padding:6px 16px; background:#52c41a; color:#fff; border:none; border-radius:3px; cursor:pointer;">+ 添加一级导航</button>
|
||
`;
|
||
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
function renderHeaderNavChildren(navIndex, children) {
|
||
// 改为表格渲染,此函数已废弃
|
||
}
|
||
|
||
// 渲染三级菜单 - 已废弃
|
||
function renderThirdLevel(navIndex, childIndex, grandChildren) {
|
||
//改为表格渲染,此函数已废弃
|
||
}
|
||
|
||
// 获取国家/地区代码列表(只使用方形图标)
|
||
function getCountryOptions() {
|
||
// 返回带中文名称的地区选项
|
||
return [
|
||
{ code: 'UN', name: '国际' },
|
||
{ code: 'CN', name: '中国' },
|
||
{ code: 'HK', name: '香港' },
|
||
{ code: 'TW', name: '台湾' },
|
||
{ code: 'MO', name: '澳门' },
|
||
{ code: 'US', name: '美国' },
|
||
{ code: 'JP', name: '日本' },
|
||
{ code: 'KR', name: '韩国' },
|
||
{ code: 'SG', name: '新加坡' },
|
||
{ code: 'GB', name: '英国' },
|
||
{ code: 'DE', name: '德国' },
|
||
{ code: 'FR', name: '法国' },
|
||
{ code: 'CA', name: '加拿大' },
|
||
{ code: 'AU', name: '澳大利亚' },
|
||
{ code: 'IN', name: '印度' },
|
||
{ code: 'TH', name: '泰国' },
|
||
{ code: 'MY', name: '马来西亚' },
|
||
{ code: 'ID', name: '印度尼西亚' },
|
||
{ code: 'PH', name: '菲律宾' },
|
||
{ code: 'VN', name: '越南' },
|
||
{ code: 'AE', name: '阿联酋' },
|
||
{ code: 'RU', name: '俄罗斯' },
|
||
{ code: 'BR', name: '巴西' },
|
||
{ code: 'IT', name: '意大利' },
|
||
{ code: 'ES', name: '西班牙' },
|
||
{ code: 'NL', name: '荷兰' },
|
||
{ code: 'CH', name: '瑞士' },
|
||
{ code: 'SE', name: '瑞典' }
|
||
];
|
||
}
|
||
|
||
// 展开/收起一级导航的子菜单
|
||
window.toggleNavChildren = function (navIndex) {
|
||
const navs = collectHeaderNav();
|
||
const nav = navs[navIndex];
|
||
if (!nav || !nav.children || nav.children.length === 0) {
|
||
// 没有子菜单,添加子菜单
|
||
addHeaderNavChild(navIndex);
|
||
return;
|
||
}
|
||
|
||
// 切换子菜单显示状态
|
||
nav.children.forEach((child, childIndex) => {
|
||
const row = document.getElementById(`nav-child-${navIndex}-${childIndex}`);
|
||
if (row) {
|
||
const isHidden = row.style.display === 'none';
|
||
row.style.display = isHidden ? 'table-row' : 'none';
|
||
|
||
// 如果收起,同时收起三级菜单
|
||
if (!isHidden && child.children) {
|
||
child.children.forEach((_, grandIndex) => {
|
||
const grandRow = document.getElementById(`nav-grandchild-${navIndex}-${childIndex}-${grandIndex}`);
|
||
if (grandRow) grandRow.style.display = 'none';
|
||
});
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
// 展开/收起三级菜单
|
||
window.toggleThirdLevel = function (navIndex, childIndex) {
|
||
const navs = collectHeaderNav();
|
||
const child = navs[navIndex]?.children?.[childIndex];
|
||
if (!child || !child.children || child.children.length === 0) {
|
||
// 没有三级菜单,添加三级菜单
|
||
addThirdLevelItem(navIndex, childIndex);
|
||
return;
|
||
}
|
||
|
||
// 切换三级菜单显示状态
|
||
child.children.forEach((_, grandIndex) => {
|
||
const row = document.getElementById(`nav-grandchild-${navIndex}-${childIndex}-${grandIndex}`);
|
||
if (row) {
|
||
row.style.display = row.style.display === 'none' ? 'table-row' : 'none';
|
||
}
|
||
});
|
||
};
|
||
|
||
// 切换三级菜单显示(旧函数保留兼容)
|
||
window.toggleHeaderNavChildren = function (navIndex) {
|
||
toggleNavChildren(navIndex);
|
||
};
|
||
|
||
// 添加三级菜单项
|
||
window.addThirdLevelItem = function (navIndex, childIndex) {
|
||
const navs = collectHeaderNav();
|
||
if (!navs[navIndex]) return;
|
||
if (!navs[navIndex].children[childIndex]) return;
|
||
|
||
if (!navs[navIndex].children[childIndex].children) {
|
||
navs[navIndex].children[childIndex].children = [];
|
||
}
|
||
|
||
navs[navIndex].children[childIndex].children.push({
|
||
name: '',
|
||
file_address: '',
|
||
description: '',
|
||
blank: false,
|
||
country_code: '' // 新增地区代码字段
|
||
});
|
||
|
||
renderHeaderNav(navs);
|
||
// 确保展开
|
||
document.getElementById(`third-level-${navIndex}-${childIndex}`).style.display = 'block';
|
||
};
|
||
|
||
// 删除三级菜单项
|
||
window.removeThirdLevelItem = function (navIndex, childIndex, grandIndex) {
|
||
const navs = collectHeaderNav();
|
||
if (navs[navIndex] && navs[navIndex].children[childIndex] && navs[navIndex].children[childIndex].children) {
|
||
navs[navIndex].children[childIndex].children.splice(grandIndex, 1);
|
||
renderHeaderNav(navs);
|
||
}
|
||
};
|
||
|
||
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] = { children: [] };
|
||
}
|
||
|
||
if (input.type === 'checkbox') {
|
||
navs[navIndex].children[childIndex][field] = input.checked;
|
||
} else {
|
||
navs[navIndex].children[childIndex][field] = input.value;
|
||
}
|
||
});
|
||
|
||
// 收集三级菜单
|
||
document.querySelectorAll('[data-hnav-grandchild]').forEach(input => {
|
||
const [navIndex, childIndex, grandIndex] = input.dataset.hnavGrandchild.split('.').map(Number);
|
||
const field = input.dataset.field;
|
||
|
||
if (!navs[navIndex]) navs[navIndex] = { children: [] };
|
||
if (!navs[navIndex].children[childIndex]) {
|
||
navs[navIndex].children[childIndex] = { children: [] };
|
||
}
|
||
if (!navs[navIndex].children[childIndex].children) {
|
||
navs[navIndex].children[childIndex].children = [];
|
||
}
|
||
if (!navs[navIndex].children[childIndex].children[grandIndex]) {
|
||
navs[navIndex].children[childIndex].children[grandIndex] = {};
|
||
}
|
||
|
||
if (input.type === 'checkbox') {
|
||
navs[navIndex].children[childIndex].children[grandIndex][field] = input.checked;
|
||
} else {
|
||
navs[navIndex].children[childIndex].children[grandIndex][field] = input.value;
|
||
}
|
||
});
|
||
|
||
// 清理空的children数组
|
||
navs.forEach(nav => {
|
||
if (nav && nav.children) {
|
||
nav.children = nav.children.filter(child => child);
|
||
nav.children.forEach(child => {
|
||
if (child && child.children) {
|
||
child.children = child.children.filter(gc => gc);
|
||
// 如果三级菜单为空,删除children属性
|
||
if (child.children.length === 0) {
|
||
delete child.children;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
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;
|
||
|
||
// 确保navs是数组
|
||
if (!Array.isArray(navs)) {
|
||
navs = [];
|
||
}
|
||
|
||
let html = `
|
||
<table class="nav-config-table" style="width:100%; border-collapse:collapse; font-size:12px;">
|
||
<thead>
|
||
<tr style="background:#f5f5f5;">
|
||
<th style="width:40px; padding:8px; border:1px solid #e0e0e0;">级</th>
|
||
<th style="width:220px; padding:8px; border:1px solid #e0e0e0;">名称</th>
|
||
<th style="width:220px; padding:8px; border:1px solid #e0e0e0;">链接</th>
|
||
<th style="width:80px; padding:8px; border:1px solid #e0e0e0;">选项</th>
|
||
<th style="width:100px; padding:8px; border:1px solid #e0e0e0;">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="footer-nav-tbody">
|
||
`;
|
||
|
||
navs.forEach((col, navIndex) => {
|
||
// 一级栏目行
|
||
html += `
|
||
<tr style="background:#fafafa; border-left:3px solid #1890ff;">
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<span style="display:inline-block; padding:2px 6px; background:#1890ff; color:#fff; border-radius:3px; font-size:10px;">1级</span>
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<input type="text" data-fnav="${navIndex}" data-field="name" value="${col.name || ''}" placeholder="栏目名称(如:热门云产品)" style="width:100%; padding:4px; border:1px solid #d9d9d9; font-size:12px;">
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8; text-align:center; color:#999;">-</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8; text-align:center; color:#999;">-</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<button onclick="toggleFooterChildren(${navIndex})" style="padding:2px 8px; background:#1890ff; color:#fff; border:none; border-radius:2px; font-size:11px; cursor:pointer; margin-right:4px;">+链接</button>
|
||
<button onclick="removeFooterNav(${navIndex})" style="padding:2px 8px; background:#ff4d4f; color:#fff; border:none; border-radius:2px; font-size:11px; cursor:pointer;">删</button>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
|
||
// 二级链接(默认隐藏)
|
||
if (col.children && col.children.length > 0) {
|
||
col.children.forEach((child, childIndex) => {
|
||
html += `
|
||
<tr id="footer-child-${navIndex}-${childIndex}" style="display:none; background:#e6f7ff; border-left:3px solid #52c41a;">
|
||
<td style="padding:6px 8px 6px 32px; border:1px solid #e8e8e8;">
|
||
<span style="display:inline-block; padding:2px 6px; background:#52c41a; color:#fff; border-radius:3px; font-size:10px;">2级</span>
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<input type="text" data-fnav-child="${navIndex}.${childIndex}" data-field="name" value="${child.name || ''}" placeholder="链接名称" style="width:100%; padding:4px; border:1px solid #d9d9d9; font-size:12px;">
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<input type="text" data-fnav-child="${navIndex}.${childIndex}" data-field="url" value="${child.url || ''}" placeholder="链接地址" style="width:100%; padding:4px; border:1px solid #d9d9d9; font-size:12px;">
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<label style="font-size:11px; margin:0;"><input type="checkbox" data-fnav-child="${navIndex}.${childIndex}" data-field="blank" ${child.blank ? 'checked' : ''}> 新窗口</label>
|
||
</td>
|
||
<td style="padding:6px 8px; border:1px solid #e8e8e8;">
|
||
<button onclick="removeFooterNavChild(${navIndex}, ${childIndex})" style="padding:2px 8px; background:#ff4d4f; color:#fff; border:none; border-radius:2px; font-size:11px; cursor:pointer;">删</button>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
});
|
||
}
|
||
});
|
||
|
||
html += `
|
||
</tbody>
|
||
</table>
|
||
<button onclick="addFooterNav()" style="margin-top:12px; padding:6px 16px; background:#1890ff; color:#fff; border:none; border-radius:3px; cursor:pointer;">+ 添加底部栏目</button>
|
||
`;
|
||
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
function renderFooterNavChildren(navIndex, children) {
|
||
// 改为表格渲染,此函数已废弃
|
||
}
|
||
|
||
// 展开/收起底部导航的子链接
|
||
window.toggleFooterChildren = function (navIndex) {
|
||
const navs = collectFooterNav();
|
||
const col = navs[navIndex];
|
||
if (!col || !col.children || col.children.length === 0) {
|
||
// 没有子链接,添加子链接
|
||
addFooterNavChild(navIndex);
|
||
return;
|
||
}
|
||
|
||
// 切换子链接显示状态
|
||
col.children.forEach((_, childIndex) => {
|
||
const row = document.getElementById(`footer-child-${navIndex}-${childIndex}`);
|
||
if (row) {
|
||
row.style.display = row.style.display === 'none' ? 'table-row' : 'none';
|
||
}
|
||
});
|
||
};
|
||
|
||
// 旧函数保留兼容
|
||
window.toggleFooterNavChildren = function (navIndex) {
|
||
toggleFooterChildren(navIndex);
|
||
};
|
||
|
||
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;
|
||
});
|
||
|
||
// 收集链接列表
|
||
document.querySelectorAll('[data-fnav-child]').forEach(input => {
|
||
const [navIndex, childIndex] = input.dataset.fnavChild.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.addFooterNav = function () {
|
||
const navs = collectFooterNav();
|
||
navs.push({ name: '', children: [] });
|
||
renderFooterNav(navs);
|
||
};
|
||
|
||
window.removeFooterNav = function (index) {
|
||
const navs = collectFooterNav();
|
||
navs.splice(index, 1);
|
||
renderFooterNav(navs);
|
||
};
|
||
|
||
window.toggleFooterNavChildren = function (index) {
|
||
const container = document.getElementById(`footer-nav-children-${index}`);
|
||
const toggleBtn = document.getElementById(`toggle-footer-${index}`);
|
||
|
||
if (container && toggleBtn) {
|
||
const isHidden = container.style.display === 'none';
|
||
container.style.display = isHidden ? 'block' : 'none';
|
||
toggleBtn.textContent = isHidden ? '收起链接列表' : '展开链接列表';
|
||
}
|
||
};
|
||
|
||
window.addFooterNavChild = function (navIndex) {
|
||
const navs = collectFooterNav();
|
||
if (!navs[navIndex]) navs[navIndex] = { children: [] };
|
||
if (!navs[navIndex].children) navs[navIndex].children = [];
|
||
|
||
navs[navIndex].children.push({
|
||
name: '',
|
||
url: '',
|
||
blank: false
|
||
});
|
||
|
||
renderFooterNav(navs);
|
||
// 确保展开
|
||
document.getElementById(`footer-nav-children-${navIndex}`).style.display = 'block';
|
||
};
|
||
|
||
window.removeFooterNavChild = function (navIndex, childIndex) {
|
||
const navs = collectFooterNav();
|
||
if (navs[navIndex] && navs[navIndex].children) {
|
||
navs[navIndex].children.splice(childIndex, 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);
|
||
// addHeaderNavBtn 按钮已整合到表格底部,无需单独绑定事件
|
||
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> |