修复插件
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
yiqiu
2025-12-28 15:46:13 +08:00
parent 4b4e5959ef
commit d741e9c693
2 changed files with 943 additions and 14 deletions

View File

@@ -180,32 +180,55 @@
</div> </div>
</section> </section>
<!-- 导航配置 --> <!-- 导航配置 -->
<section id="tab-nav" class="config-section"> <section id="tab-nav" class="config-section">
<div class="section-card"> <div class="section-card">
<div class="section-header"> <div class="section-header">
<h2>导航配置</h2> <h2>顶部导航</h2>
<p class="section-desc">顶部和底部导航较复杂,建议使用JSON编辑器编辑</p>
</div> </div>
<div class="section-body"> <div class="section-body">
<div class="alert alert-info"> <div id="headerNavList"></div>
由于导航结构包含多级嵌套,建议切换到"JSON编辑器"Tab进行编辑 <button class="btn btn-secondary" id="addHeaderNavBtn">+ 添加导航</button>
</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>
</div> </div>
</section> </section>
<!-- 其他配置 --> <!-- 其他配置 -->
<section id="tab-other" class="config-section"> <section id="tab-other" class="config-section">
<div class="section-card"> <div class="section-card">
<div class="section-header"> <div class="section-header">
<h2>其他配置</h2> <h2>友情链接</h2>
<p class="section-desc">友情链接、反馈类型等其他配置项</p>
</div> </div>
<div class="section-body"> <div class="section-body">
<div class="alert alert-info"> <div id="friendlyLinkList"></div>
这些配置项建议使用JSON编辑器编辑,格式请参考完整JSON数据 <button class="btn btn-secondary" id="addFriendlyLinkBtn">+ 添加</button>
</div> </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>
</div> </div>
</section> </section>
@@ -293,6 +316,15 @@
// 渲染荣誉列表 // 渲染荣誉列表
renderHonors(data.honor || []); renderHonors(data.honor || []);
// 渲染导航
renderHeaderNav(data.header_nav || []);
renderFooterNav(data.footer_nav || []);
// 渲染其他配置
renderFriendlyLinks(data.friendly_link || []);
renderSides(data.side || []);
renderFeedbackTypes(data.feedback_type || []);
} }
// 获取嵌套属性值 // 获取嵌套属性值
@@ -322,6 +354,11 @@
// 收集荣誉数据 // 收集荣誉数据
data.honor = collectHonors(); data.honor = collectHonors();
data.friendly_link = collectFriendlyLinks();
data.side = collectSides();
data.feedback_type = collectFeedbackTypes();
data.header_nav = collectHeaderNav();
data.footer_nav = collectFooterNav();
return data; return data;
} }
@@ -454,12 +491,302 @@
renderHonors(honors); 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 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="removeHeaderNav(${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-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 || ''}">
</div>
<div class="form-item">
<div style="font-size:13px;color:#666;">子菜单请使用JSON编辑器配置</div>
</div>
</div>
</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;
});
// 保留原有的children
const orig = config.header_nav || [];
navs.forEach((nav, i) => {
if(orig[i] && orig[i].children) {
nav.children = orig[i].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);
};
// ========== 底部导航(简化) ==========
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) => { document.addEventListener('click', (e) => {
if (e.target.closest('.upload-btn')) { if (e.target.closest('.upload-btn')) {
e.preventDefault(); e.preventDefault();
const btn = e.target.closest('.upload-btn'); const btn = e.target.closest('.upload-btn');
currentUploadTarget = btn.dataset.target || btn.dataset.targetBanner || btn.dataset.targetHonor; currentUploadTarget = btn.dataset.target || btn.dataset.targetBanner || btn.dataset.targetHonor || btn.dataset.targetSide;
document.getElementById('fileInput').click(); document.getElementById('fileInput').click();
} }
}); });
@@ -517,11 +844,16 @@
const data = collectFormData(); const data = collectFormData();
console.log('保存的数据:', data); // 调试日志
axios.post(apiBase, data, { axios.post(apiBase, data, {
headers: { Authorization: `Bearer ${token}` } headers: { Authorization: `Bearer ${token}` }
}).then(res => { }).then(res => {
alert(res.data.msg || '保存成功'); alert(res.data.msg || '保存成功');
loadConfig(); // 重新加载 // 使用服务器返回的数据更新config
config = res.data.data || data;
// 重新渲染界面
fillForm(config);
}).catch(err => { }).catch(err => {
alert('保存失败: ' + (err.response?.data?.msg || err.message)); alert('保存失败: ' + (err.response?.data?.msg || err.message));
}).finally(() => { }).finally(() => {
@@ -533,6 +865,11 @@
// 添加按钮事件 // 添加按钮事件
document.getElementById('addBannerBtn').addEventListener('click', addBanner); document.getElementById('addBannerBtn').addEventListener('click', addBanner);
document.getElementById('addHonorBtn').addEventListener('click', addHonor); 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编辑器功能 // JSON编辑器功能
document.getElementById('syncJsonBtn').addEventListener('click', () => { document.getElementById('syncJsonBtn').addEventListener('click', () => {

View File

@@ -0,0 +1,592 @@
<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>
<p class="section-desc">顶部和底部导航较复杂,建议使用JSON编辑器编辑</p>
</div>
<div class="section-body">
<div class="alert alert-info">
由于导航结构包含多级嵌套,建议切换到"JSON编辑器"Tab进行编辑
</div>
</div>
</div>
</section>
<!-- 其他配置 -->
<section id="tab-other" class="config-section">
<div class="section-card">
<div class="section-header">
<h2>其他配置</h2>
<p class="section-desc">友情链接、反馈类型等其他配置项</p>
</div>
<div class="section-body">
<div class="alert alert-info">
这些配置项建议使用JSON编辑器编辑,格式请参考完整JSON数据
</div>
</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() {
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 || []);
}
// 获取嵌套属性值
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();
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);
};
// 文件上传
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;
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); // 调试日志
axios.post(apiBase, data, {
headers: { Authorization: `Bearer ${token}` }
}).then(res => {
alert(res.data.msg || '保存成功');
// 使用服务器返回的数据更新config
config = res.data.data || data;
// 重新渲染界面
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);
// 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>