feat(admin): 备案号可编辑,页脚模板由服务端生成

备案号原来只能写死在 pageInfo.footer 的 HTML 里。现在 /admin/ 站点信息
拆出「ICP 备案号」「公安备案号」两个输入框,页脚 HTML 由服务端
beianFooter() 统一按模板生成(含 html.EscapeString 转义),
自定义页脚 HTML 降级为「高级」折叠项;旧 footer 会被解析回填到字段。

conf.example.yml 里重复的 .navi-search-bar 规则已由 web/navi.css 接管,删除。
This commit is contained in:
JianFeeeee
2026-09-11 16:25:26 +08:00
parent 9eb8214fab
commit e4967b99ee
4 changed files with 134 additions and 5 deletions

View File

@ -88,10 +88,16 @@ details summary{cursor:pointer;font-size:13px;font-weight:600;color:var(--tx2)}
<div class="fg"><label>标题</label><input id="p-title" placeholder="JianF 统一门户"></div>
<div class="fg"><label>副标题</label><input id="p-desc" placeholder="内网服务导航"></div>
</div>
<div class="fg" style="margin-bottom:12px">
<label>页脚(可含 HTML)</label>
<textarea id="p-footer" placeholder="留空则不显示"></textarea>
<div class="row">
<div class="fg"><label>ICP 备案号</label><input id="p-icp" placeholder="京ICP备00000000号-1"></div>
<div class="fg"><label>公安备案号(可选)</label><input id="p-gongan" placeholder="京公网安备00000000000000号"></div>
</div>
<details style="margin:12px 0">
<summary style="cursor:pointer;font-size:13px;color:#475569">高级:自定义页脚 HTML</summary>
<div class="fg" style="margin-top:8px">
<textarea id="p-footer" placeholder="留空则按上面的备案号自动生成;填写后以这里为准"></textarea>
</div>
</details>
<label>顶部导航链接</label>
<div id="navlinks" style="margin:8px 0 10px"></div>
<button class="ghost icon-btn" id="add-link">+ 添加链接</button>
@ -237,6 +243,17 @@ function renderNavLinks() {
});
}
/* ── 备案号 ──
备案号直接编辑,页脚 HTML 由服务端模板生成;高级模式下可整段覆盖。 */
function parseIcp(html) {
const m = /[\u4e00-\u9fa5]{2}ICP备\d+号(-\d+)?/.exec(html || '');
return m ? m[0] : '';
}
function parseGongan(html) {
const m = /[\u4e00-\u9fa5]{2}公网安备\d+号/.exec(html || '');
return m ? m[0] : '';
}
async function loadPageInfo() {
try {
const r = await fetch(A + '/pageinfo', { headers: authHeaders() });
@ -248,7 +265,12 @@ async function loadPageInfo() {
}
document.getElementById('p-title').value = PAGE.title || '';
document.getElementById('p-desc').value = PAGE.description || '';
document.getElementById('p-footer').value = PAGE.footer || '';
const icp = PAGE.icp || parseIcp(PAGE.footer);
const gongan = PAGE.gongan || parseGongan(PAGE.footer);
document.getElementById('p-icp').value = icp;
document.getElementById('p-gongan').value = gongan;
// 能从备案号字段还原时,高级 HTML 留空,保存时按模板重新生成
document.getElementById('p-footer').value = (icp || gongan) ? '' : (PAGE.footer || '');
if (!PAGE.navLinks && PAGE.navLinks !== null) PAGE.navLinks = [];
renderNavLinks();
}
@ -263,6 +285,9 @@ document.getElementById('save-info').addEventListener('click', async () => {
const body = {
title: document.getElementById('p-title').value,
description: document.getElementById('p-desc').value,
icp: document.getElementById('p-icp').value,
gongan: document.getElementById('p-gongan').value,
// 留空 = 交给服务端按备案号生成模板;填写则整段覆盖
footer: document.getElementById('p-footer').value,
navLinks: (PAGE.navLinks || []).filter(l => l && (l.name || l.url)),
};