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

This commit is contained in:
yiqiu
2025-12-28 14:26:13 +08:00
parent 5c7ae12f83
commit 957ac89c4d
3 changed files with 1007 additions and 4 deletions

View File

@@ -48,6 +48,32 @@
</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>
<!-- 主内容区 -->
@@ -153,6 +179,90 @@
</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>
<div class="section-card">
<div class="section-header">
<h2>合作伙伴</h2>
</div>
<div class="section-body">
<div id="partnerList"></div>
<button class="btn btn-secondary" id="addPartnerBtn">+ 添加</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>
@@ -215,6 +325,16 @@
// 渲染荣誉列表
renderHonors(data.honor || []);
// 渲染导航
renderHeaderNav(data.header_nav || []);
renderFooterNav(data.footer_nav || []);
// 渲染其他配置
renderFriendlyLinks(data.friendly_link || []);
renderSides(data.side || []);
renderFeedbackTypes(data.feedback_type || []);
renderPartners(data.partner || []);
}
// 获取嵌套属性值
@@ -239,11 +359,15 @@
setNestedValue(data, name, input.value);
});
// 收集轮播数据
// 收集动态列表数据
data.banner = collectBanners();
// 收集荣誉数据
data.honor = collectHonors();
data.friendly_link = collectFriendlyLinks();
data.side = collectSides();
data.feedback_type = collectFeedbackTypes();
data.partner = collectPartners();
data.header_nav = collectHeaderNav();
data.footer_nav = collectFooterNav();
return data;
}
@@ -376,12 +500,266 @@
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 renderPartners(partners) {
const container = document.getElementById('partnerList');
container.innerHTML = '';
partners.forEach((partner, 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="removePartner(${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-partner="${index}" data-field="name" value="${partner.name || ''}" placeholder="合作伙伴名称">
</div>
<div class="form-item">
<label>Logo地址</label>
<div class="upload-control">
<input type="text" class="form-control" data-partner="${index}" data-field="img" value="${partner.img || ''}" placeholder="/upload/partner.png">
<button class="btn btn-secondary upload-btn" data-target-partner="${index}.img">选择文件</button>
</div>
</div>
<div class="form-item">
<label>描述</label>
<input type="text" class="form-control" data-partner="${index}" data-field="description" value="${partner.description || ''}" placeholder="简介">
</div>
</div>
</div>
`;
container.appendChild(item);
});
}
function collectPartners() {
const partners = [];
document.querySelectorAll('[data-partner]').forEach(input => {
const index = parseInt(input.dataset.partner);
const field = input.dataset.field;
if (!partners[index]) partners[index] = {};
partners[index][field] = input.value;
});
return partners.filter(p => p);
}
window.addPartner = function () {
const partners = collectPartners();
partners.push({ name: '', img: '', description: '' });
renderPartners(partners);
};
window.removePartner = function (index) {
const partners = collectPartners();
partners.splice(index, 1);
renderPartners(partners);
};
// ========== 顶部导航(简化版) ==========
function renderHeaderNav(navs) {
const container = document.getElementById('headerNavList');
container.innerHTML = '<div class="alert alert-info">顶部导航较复杂,建议使用JSON编辑器编辑</div>';
}
function collectHeaderNav() {
return config.header_nav || [];
}
window.addHeaderNav = function () {
alert('请使用JSON编辑器编辑顶部导航');
};
// ========== 底部导航(简化版) ==========
function renderFooterNav(navs) {
const container = document.getElementById('footerNavList');
container.innerHTML = '<div class="alert alert-info">底部导航较复杂,建议使用JSON编辑器编辑</div>';
}
function collectFooterNav() {
return config.footer_nav || [];
}
window.addFooterNav = function () {
alert('请使用JSON编辑器编辑底部导航');
};
// 文件上传
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;
currentUploadTarget = btn.dataset.target || btn.dataset.targetBanner || btn.dataset.targetHonor || btn.dataset.targetSide || btn.dataset.targetPartner;
document.getElementById('fileInput').click();
}
});
@@ -455,6 +833,57 @@
// 添加按钮事件
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('addPartnerBtn').addEventListener('click', addPartner);
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();