[CMS-VISUAL] D1-D10 后端: 新组件数据槽(banner 扩 topic/event 位 5 列 + cms_person 新表+CRUD+防跨站)+ 6 区块类型校验与装配(空数据整块不渲染 R-44)+ 骨架新组件 fragment/内置 22 图标集/共享动效 JS(reveal+count-up R-42)+ searchStyle 搜索浮层(D-10,含坑④条件包裹修复)+ 三保留主题 4 新参数

This commit is contained in:
chenxing
2026-09-28 14:04:19 +08:00
parent 089cb54011
commit 4cb435b715
23 changed files with 1673 additions and 52 deletions
@@ -0,0 +1,77 @@
<!--
D-10(docs/20)搜索浮层 · 通用 fragment:theme.json 参数 searchStyle 控制门户搜索形态——
· inline(缺省)= 保留主题 header 自有内联搜索表单(现状外观,零变化)
· none = 不渲染任何搜索入口
· overlay = 隐藏内联表单,渲染右上「放大镜」按钮 + 全屏搜索浮层(本文件实现)
引用方式:主题 header.html 在其内联搜索表单位置——
<th:block th:if="${themeParams != null and themeParams['searchStyle'] == 'overlay'}"
th:replace="~{'themes/common/fragments/search' :: overlay(${url})}" />
<form class="…" th:unless="${themeParams != null and themeParams['searchStyle'] == 'overlay'}" …>(inline/缺省保留原表单)
样式/脚本自持(不引外部依赖);颜色跟随主题变量缺省蓝。
-->
<th:block th:fragment="overlay(url)">
<div class="plb-search-widget">
<button type="button" class="plb-search-toggle" aria-label="打开站内搜索" aria-expanded="false">
<svg viewBox="0 0 24 24" width="19" height="19" focusable="false" aria-hidden="true">
<path d="M10.5 3a7.5 7.5 0 015.9 12.1l4.3 4.3-1.4 1.4-4.3-4.3A7.5 7.5 0 1110.5 3zm0 2a5.5 5.5 0 100 11 5.5 5.5 0 000-11z"
fill="currentColor"></path>
</svg>
<span>搜索</span>
</button>
<div class="plb-search-layer" role="dialog" aria-modal="true" aria-label="站内搜索">
<button type="button" class="plb-search-close" aria-label="关闭搜索">✕</button>
<form class="plb-search-form" th:action="${url.search('', null)}" method="get" role="search">
<input type="text" name="q" placeholder="输入关键词,搜索本站内容…" aria-label="站内搜索" autocomplete="off">
<button type="submit">搜 索</button>
</form>
<p class="plb-search-tip">按 ESC 或点击空白处关闭</p>
</div>
</div>
<style>
.plb-search-widget{position:relative}
.plb-search-toggle{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--border, #e5e6eb);
background:transparent;color:inherit;border-radius:18px;padding:5px 14px;font-size:13px;cursor:pointer;
transition:border-color .2s,color .2s}
.plb-search-toggle:hover{border-color:var(--primary, #2d8cf0);color:var(--primary, #2d8cf0)}
.plb-search-layer{position:fixed;inset:0;z-index:9999;display:none;align-items:center;justify-content:center;
flex-direction:column;background:rgba(10,20,40,.82);backdrop-filter:blur(6px)}
.plb-search-widget.open .plb-search-layer{display:flex}
.plb-search-close{position:absolute;top:26px;right:32px;background:none;border:0;color:#fff;font-size:22px;
cursor:pointer;opacity:.8;padding:6px}
.plb-search-close:hover{opacity:1}
.plb-search-form{display:flex;width:min(640px,86vw);border-radius:8px;overflow:hidden;box-shadow:0 18px 60px rgba(0,0,0,.35)}
.plb-search-form input{flex:1;border:0;padding:16px 18px;font-size:16px;outline:none;min-width:0}
.plb-search-form button{border:0;padding:0 26px;font-size:15px;cursor:pointer;
background:var(--primary, #2d8cf0);color:#fff}
.plb-search-form button:hover{filter:brightness(1.08)}
.plb-search-tip{margin-top:16px;color:rgba(255,255,255,.55);font-size:12px;letter-spacing:.05em}
</style>
<script>
(function () {
var widget = document.currentScript && document.currentScript.previousElementSibling;
while (widget && !(widget.classList && widget.classList.contains('plb-search-widget'))) {
widget = widget && widget.previousElementSibling;
}
if (!widget || widget.__plbSearchBound) { return; }
widget.__plbSearchBound = true;
var toggle = widget.querySelector('.plb-search-toggle');
var layer = widget.querySelector('.plb-search-layer');
var input = widget.querySelector('.plb-search-form input');
function open() {
widget.classList.add('open');
toggle.setAttribute('aria-expanded', 'true');
if (input) { setTimeout(function () { input.focus(); }, 50); }
}
function close() {
widget.classList.remove('open');
toggle.setAttribute('aria-expanded', 'false');
}
toggle.addEventListener('click', open);
widget.querySelector('.plb-search-close').addEventListener('click', close);
layer.addEventListener('click', function (e) { if (e.target === layer) { close(); } });
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') { close(); }
});
})();
</script>
</th:block>