feat: 导航页文案可后台配置 + webhook修复 + 企业微信通知可用
- 导航页所有文案(标题/副标题/标签/咨询区/页脚/工作室名称)均可在后台「导航页设置」页配置 - 新增公开API GET /api/v1/nav-settings,导航页加载时自动拉取配置覆盖默认值 - CustomerHandler改为从数据库读取webhook URL,不再依赖config.yaml - 新增默认企业微信webhook地址,开箱即用 - 后台新增webhook测试专用接口 POST /api/v1/admin/webhook/test - 后台新增批量配置保存接口 PUT /api/v1/admin/config-batch - 后台新增「导航页设置」管理页面,支持分组编辑保存 - 页脚添加工作室名称展示位
This commit is contained in:
145
web/dist/admin/index.html
vendored
145
web/dist/admin/index.html
vendored
@@ -273,6 +273,9 @@ tbody tr:last-child td{border-bottom:none}
|
||||
<a class="sidebar-link" onclick="navigate('webhook')" data-page="webhook">
|
||||
<span class="icon">🔔</span> Webhook配置
|
||||
</a>
|
||||
<a class="sidebar-link" onclick="navigate('navsettings')" data-page="navsettings">
|
||||
<span class="icon">📝</span> 导航页设置
|
||||
</a>
|
||||
<a class="sidebar-link" onclick="navigate('settings')" data-page="settings">
|
||||
<span class="icon">⚙️</span> 系统设置
|
||||
</a>
|
||||
@@ -455,7 +458,7 @@ function navigate(page, params = {}) {
|
||||
document.querySelectorAll('.sidebar-link').forEach(el => el.classList.remove('active'));
|
||||
const link = document.querySelector(`[data-page="${page}"]`);
|
||||
if (link) link.classList.add('active');
|
||||
const titles = { users: '用户管理', content: '内容管理', settings: '系统设置', sites: '站点导航', accesslog: '访问日志', webhook: 'Webhook配置' };
|
||||
const titles = { users: '用户管理', content: '内容管理', settings: '系统设置', sites: '站点导航', accesslog: '访问日志', webhook: 'Webhook配置', navsettings: '导航页设置' };
|
||||
document.getElementById('pageTitle').textContent = titles[page] || page;
|
||||
if (page === 'users') renderUsers();
|
||||
else if (page === 'content') renderContent(params.table);
|
||||
@@ -463,6 +466,7 @@ function navigate(page, params = {}) {
|
||||
else if (page === 'sites') renderSites();
|
||||
else if (page === 'accesslog') renderAccessLogs();
|
||||
else if (page === 'webhook') renderWebhook();
|
||||
else if (page === 'navsettings') renderNavSettings();
|
||||
if (window.innerWidth <= 768) document.getElementById('sidebar').classList.remove('open');
|
||||
}
|
||||
|
||||
@@ -1258,33 +1262,126 @@ async function testWebhook() {
|
||||
resultEl.style.display = 'block';
|
||||
resultEl.innerHTML = '<div style="color:var(--text-muted)">发送中...</div>';
|
||||
try {
|
||||
await api('/admin/config/wechat_webhook_url', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ value: document.getElementById('webhookUrl').value.trim(), remark: '企业微信群机器人Webhook地址' })
|
||||
});
|
||||
// 通过客户咨询接口发送测试消息
|
||||
const resp = await fetch(API_BASE + '/customer/submit', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: '测试用户',
|
||||
phone: '13800000000',
|
||||
company: '测试公司',
|
||||
email: 'test@example.com',
|
||||
needs: '这是一条测试消息,用于验证 Webhook 配置是否正确'
|
||||
})
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (data.code === 0) {
|
||||
resultEl.innerHTML = '<div style="color:var(--success);font-weight:600">✅ 测试消息已发送,请检查企业微信群</div>';
|
||||
} else {
|
||||
resultEl.innerHTML = `<div style="color:var(--danger)">❌ 发送失败: ${data.message}</div>`;
|
||||
}
|
||||
await api('/admin/webhook/test', { method: 'POST' });
|
||||
resultEl.innerHTML = '<div style="color:var(--success);font-weight:600">✅ 测试消息已发送,请检查企业微信群</div>';
|
||||
} catch (e) {
|
||||
resultEl.innerHTML = '<div style="color:var(--danger)">❌ 网络错误</div>';
|
||||
resultEl.innerHTML = `<div style="color:var(--danger)">❌ ${e.message || '发送失败'}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 导航页设置 =====
|
||||
async function renderNavSettings() {
|
||||
const el = document.getElementById('content');
|
||||
el.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
|
||||
try {
|
||||
const data = await api('/admin/config');
|
||||
const configs = {};
|
||||
(data.data || []).forEach(c => { configs[c.key] = c.value; });
|
||||
el.innerHTML = `
|
||||
<div class="table-card">
|
||||
<div class="section-header" style="padding:16px"><h4>📝 导航页文案设置</h4></div>
|
||||
<div style="padding:24px;max-width:800px">
|
||||
<div class="form-group">
|
||||
<label>页面标题 (左侧)</label>
|
||||
<input type="text" id="ns_hero_title" value="${configs.nav_hero_title||''}" placeholder="Gotest">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>页面标题 (右侧渐变)</label>
|
||||
<input type="text" id="ns_hero_highlight" value="${configs.nav_hero_highlight||''}" placeholder="模板导航">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>副标题描述</label>
|
||||
<input type="text" id="ns_hero_desc" value="${configs.nav_hero_desc||''}" placeholder="6 大行业,19 套精心设计的网站模板,一键切换,即刻预览">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>标签 - 模板数</label>
|
||||
<input type="text" id="ns_pill_templates" value="${configs.nav_pill_templates||''}" placeholder="19">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>标签 - 行业数</label>
|
||||
<input type="text" id="ns_pill_lines" value="${configs.nav_pill_lines||''}" placeholder="6">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>标签 - 每行业模板数</label>
|
||||
<input type="text" id="ns_pill_per_industry" value="${configs.nav_pill_per_industry||''}" placeholder="3">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>标签 - 第四项</label>
|
||||
<input type="text" id="ns_pill_responsive" value="${configs.nav_pill_responsive||''}" placeholder="响应式设计">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card" style="margin-top:16px">
|
||||
<div class="section-header" style="padding:16px"><h4>📂 分类标题</h4></div>
|
||||
<div style="padding:24px;max-width:800px">
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>企业官网 - 标题</label><input type="text" id="ns_sec_enterprise_title" value="${configs.nav_sec_enterprise_title||''}" placeholder="企业官网"></div>
|
||||
<div class="form-group"><label>企业官网 - 副标题</label><input type="text" id="ns_sec_enterprise_sub" value="${configs.nav_sec_enterprise_sub||''}" placeholder="3 套风格"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>跨境电商 - 标题</label><input type="text" id="ns_sec_shop_title" value="${configs.nav_sec_shop_title||''}" placeholder="跨境电商"></div>
|
||||
<div class="form-group"><label>跨境电商 - 副标题</label><input type="text" id="ns_sec_shop_sub" value="${configs.nav_sec_shop_sub||''}" placeholder="4 套风格"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>外卖平台 - 标题</label><input type="text" id="ns_sec_food_title" value="${configs.nav_sec_food_title||''}" placeholder="外卖平台"></div>
|
||||
<div class="form-group"><label>外卖平台 - 副标题</label><input type="text" id="ns_sec_food_sub" value="${configs.nav_sec_food_sub||''}" placeholder="3 套风格"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>游戏平台 - 标题</label><input type="text" id="ns_sec_game_title" value="${configs.nav_sec_game_title||''}" placeholder="游戏平台"></div>
|
||||
<div class="form-group"><label>游戏平台 - 副标题</label><input type="text" id="ns_sec_game_sub" value="${configs.nav_sec_game_sub||''}" placeholder="3 套风格"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>应用商店 - 标题</label><input type="text" id="ns_sec_app_title" value="${configs.nav_sec_app_title||''}" placeholder="应用商店"></div>
|
||||
<div class="form-group"><label>应用商店 - 副标题</label><input type="text" id="ns_sec_app_sub" value="${configs.nav_sec_app_sub||''}" placeholder="3 套风格"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>音乐平台 - 标题</label><input type="text" id="ns_sec_music_title" value="${configs.nav_sec_music_title||''}" placeholder="音乐平台"></div>
|
||||
<div class="form-group"><label>音乐平台 - 副标题</label><input type="text" id="ns_sec_music_sub" value="${configs.nav_sec_music_sub||''}" placeholder="3 套风格"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card" style="margin-top:16px">
|
||||
<div class="section-header" style="padding:16px"><h4>💬 咨询区 & 页脚</h4></div>
|
||||
<div style="padding:24px;max-width:800px">
|
||||
<div class="form-group"><label>咨询区 - 标题</label><input type="text" id="ns_consult_title" value="${configs.nav_consult_title||''}" placeholder="需要定制网站?"></div>
|
||||
<div class="form-group"><label>咨询区 - 描述</label><input type="text" id="ns_consult_desc" value="${configs.nav_consult_desc||''}" placeholder="只需几百块,即可拥有同款精美站点!留下您的信息,我们尽快联系您"></div>
|
||||
<div class="form-group"><label>提交成功文案</label><input type="text" id="ns_consult_success" value="${configs.nav_consult_success||''}" placeholder="提交成功!"></div>
|
||||
<div class="form-group"><label>页脚文案</label><input type="text" id="ns_footer_text" value="${configs.nav_footer_text||''}" placeholder="Gotest · 多行业网站模板集合 · 点击卡片进入对应模板"></div>
|
||||
<div class="form-group"><label>工作室/公司名称</label><input type="text" id="ns_company_name" value="${configs.nav_company_name||''}" placeholder="焉耆青梧映星软件开发工作室"></div>
|
||||
<div style="margin-top:20px">
|
||||
<button class="btn btn-primary" onclick="saveNavSettings()">保存所有设置</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
} catch (e) { el.innerHTML = '<div class="loading">加载失败</div>'; }
|
||||
}
|
||||
|
||||
async function saveNavSettings() {
|
||||
const fields = [
|
||||
'hero_title','hero_highlight','hero_desc','pill_templates','pill_lines','pill_per_industry','pill_responsive',
|
||||
'sec_enterprise_title','sec_enterprise_sub','sec_shop_title','sec_shop_sub',
|
||||
'sec_food_title','sec_food_sub','sec_game_title','sec_game_sub',
|
||||
'sec_app_title','sec_app_sub','sec_music_title','sec_music_sub',
|
||||
'consult_title','consult_desc','consult_success','footer_text','company_name'
|
||||
];
|
||||
const items = fields.map(f => ({
|
||||
key: 'nav_' + f,
|
||||
value: (document.getElementById('ns_' + f)?.value || '').trim(),
|
||||
remark: '导航页配置'
|
||||
}));
|
||||
try {
|
||||
await api('/admin/config-batch', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ items })
|
||||
});
|
||||
toast('保存成功!刷新导航页即可看到效果', 'success');
|
||||
} catch (e) { toast('保存失败', 'error'); }
|
||||
}
|
||||
|
||||
// ===== 通用工具 =====
|
||||
function formatDate(d) {
|
||||
if (!d) return '-';
|
||||
|
||||
Reference in New Issue
Block a user