feat(header): add 叠加组件 with after_html per background item

「顶部多功能组件 → 背景内容」的每个背景项目新增「叠加组件」手风琴,
其中提供「组件下方额外内容」(after_html) 输入框,支持 HTML。

- 后台 schema:slider_item_fields 增加 overlay accordion(sanitize_type html),
  默认值与 PageConfigMetaBox / DisplayPageGroups 的默认项目同步带上空 overlay
- 保存:页面独立配置(page_config 元数据)按项目写入 overlay.after_html,
  沿用 wp_kses_post() 过滤
- 前端:after_html 作为 .slide-overlay-after 覆盖层输出在所属幻灯片内
  (幻灯片是 overflow:hidden 的定位层,放到幻灯片外会被裁掉),
  默认贴底居中、仅其中的链接与表单接收点击
- main.min.css 由 tools/build-public-assets.mjs 重新生成

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Opus 5 committed 2026-11-09 19:44:54 +08:00
1 parent 44a03fd5f4
commit c2733017fd
10 files changed
+89 -4

No files matched your search

+25
View File
@@ -6816,6 +6816,31 @@ body.nav-fixed .slide-header {
z-index: 1;
}
/*
* 叠加组件(每个背景项目独立的 after_html)。
* 幻灯片本身是 overflow:hidden 的绝对定位层,附加内容只能作为幻灯片内的
* 覆盖层存在:贴底、居中、压在背景之上,链接与按钮保持可点击。
*/
.slide-overlay-after {
position: absolute;
right: 0;
bottom: 0;
left: 0;
z-index: 3;
padding: 1.2em;
color: #fff;
text-align: center;
pointer-events: none;
}
.slide-overlay-after a,
.slide-overlay-after button,
.slide-overlay-after input,
.slide-overlay-after select,
.slide-overlay-after textarea {
pointer-events: auto;
}
.header-slider-card .jio-panel {
margin-bottom: 0;
}
+1 -1
View File
@@ -1 +1 @@
body{--mian-max-width: 1200px;--posts-card-scale: 70%;--posts-list-scale: 70%;--theme-color: #f04494;--focus-color: var(--theme-color);--focus-shadow-color: rgba(253, 83, 161, .4);--key-color: #333;--main-color: #4e5358;--main-shadow: rgba(116, 116, 116, .08);--muted-color: #777;--muted-2-color: #999;--muted-3-color: #b1b1b1;--muted-4-color: #d2d2d2;--body-bg-color: #f5f6f7;--main-bg-color: #fff;--muted-bg-color: #eee;--muted-bg-2-color: #f6f7f8;--main-border-color: rgba(50, 50, 50, .06);--muted-border-color: rgba(0, 0, 0, .03);--main-radius: 8px;--mini-radius: calc(var(--main-radius) / 1.4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}.dark-theme{--key-color: #f8fafc;--main-color: #e5eef7;--main-shadow: rgba(24, 24, 24, .1);--muted-color: #b4b6bb;--muted-2-color: #888a8f;--muted-3-color: #636469;--muted-4-color: #43454a;--body-bg-color: #292a2d;--main-bg-color: #323335;--muted-bg-color: #2d2e31;--muted-bg-2-color: #37383a;--main-border-color: rgba(114, 114, 114, .1);--muted-border-color: rgba(184, 184, 184, .02);--blur-bg: rgba(50, 51, 53, .8);--float-btn-bg: rgba(70, 70, 70, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}.dark::view-transition-old(root){z-index:1}.dark::view-transition-new(root){z-index:2147483646}::view-transition-old(root){z-index:2147483646}::view-transition-new(root){z-index:1}@supports not ((-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(20px))){body{--blur-bg: #ffffff}body.dark-theme{--blur-bg: #37383a}}body:has(.modal.in),body.modal-open{--blur-bg: #ffffff}body.dark-theme:has(.modal.in),body.dark-theme.modal-open{--blur-bg: #37383a}body.fps-accelerat{--blur-bg: #ffffff}body.fps-accelerat.dark-theme{--blur-bg: #37383a}body.fps-accelerat *{-webkit-backdrop-filter:unset!important;backdrop-filter:unset!important}a[href],.relates-thumb li a>.post-info,.more-posts a>span{transition:color .2s,background .3s,opacity .3s,box-shadow .3s,transform .3s}.main-shadow{transition:box-shadow .3s}.but:focus,.but:hover,.progress,.navbar-nav>li:before{transition:.3s}.navbar-top .sub-menu,.theme-popover,.hover-show .hover-show-con{opacity:0;transform:translateY(6px);transition:.4s}.navbar-top li:hover>.sub-menu,.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{opacity:1;transform:translateY(0)}.hover-show{position:relative;cursor:pointer;z-index:10}.hover-show .hover-show-con{position:absolute;visibility:hidden}.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{visibility:unset}.match-ok,.navbar-top .sub-menu li>a,.ic-close{transition:.3s}.ic-close:hover{transform:rotate(270deg);color:inherit}.social_loginbar a,.item-thumbnail img,.dosc-nav .fa{transition:.2s ease-out}.hover-zoom:hover,.hover-zoom-img:hover img,.social_loginbar .xh-social-item:hover,.social_loginbar .social-login-item:hover{transform:scale(1.05)}.hover-zoom-sm:hover,.hover-zoom-img-sm:hover img,.posts-mini:hover img,.posts-item:hover .item-thumbnail img,.posts-item.mult-thumb .thumb-items>span>img:hover{transform:scale(1.02)}.main-min-height{min-height:calc(100vh - 280px)}.main-shadow{box-shadow:0 0 10px var(--main-shadow)}.key-color{color:var(--key-color)}.focus-color{--this-color: var(--focus-color);color:var(--focus-color)}.muted-color{color:var(--muted-color)}.muted-2-color{color:var(--muted-2-color)}.muted-3-color{color:var(--muted-3-color)}.main-bg{background:var(--main-bg-color)}.placeholder{background:linear-gradient(118deg,#7979791a 25%,#79797933 37%,#7979791a 63%);opacity:1;border-radius:4px;-webkit-animation:placeholder 1.5s ease infinite!important;animation:placeholder 1.5s ease infinite!important;background-size:400% 100%!important}.placeholder.img{height:119px}.placeholder.t1{height:24px;width:80%}.placeholder.k1{height:20px;width:60%}.placeholder.k2{height:20px}.placeholder.k3{height:15px}.placeholder.s1{width:70px;height:15px;display:inline-block}@-webkit-keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@-webkit-keyframes fade{0%{opacity:0}to{opacity:1}}@keyframes fade{0%{opacity:0}to{opacity:1}}.ani.shake{-webkit-animation:shake .3s ease-in!important;animation:shake .3s ease-in!important}@-webkit-keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}@keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}.obs-animate{opacity:0;transition:opacity .6sLine truncated
body{--mian-max-width: 1200px;--posts-card-scale: 70%;--posts-list-scale: 70%;--theme-color: #f04494;--focus-color: var(--theme-color);--focus-shadow-color: rgba(253, 83, 161, .4);--key-color: #333;--main-color: #4e5358;--main-shadow: rgba(116, 116, 116, .08);--muted-color: #777;--muted-2-color: #999;--muted-3-color: #b1b1b1;--muted-4-color: #d2d2d2;--body-bg-color: #f5f6f7;--main-bg-color: #fff;--muted-bg-color: #eee;--muted-bg-2-color: #f6f7f8;--main-border-color: rgba(50, 50, 50, .06);--muted-border-color: rgba(0, 0, 0, .03);--main-radius: 8px;--mini-radius: calc(var(--main-radius) / 1.4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}.dark-theme{--key-color: #f8fafc;--main-color: #e5eef7;--main-shadow: rgba(24, 24, 24, .1);--muted-color: #b4b6bb;--muted-2-color: #888a8f;--muted-3-color: #636469;--muted-4-color: #43454a;--body-bg-color: #292a2d;--main-bg-color: #323335;--muted-bg-color: #2d2e31;--muted-bg-2-color: #37383a;--main-border-color: rgba(114, 114, 114, .1);--muted-border-color: rgba(184, 184, 184, .02);--blur-bg: rgba(50, 51, 53, .8);--float-btn-bg: rgba(70, 70, 70, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}.dark::view-transition-old(root){z-index:1}.dark::view-transition-new(root){z-index:2147483646}::view-transition-old(root){z-index:2147483646}::view-transition-new(root){z-index:1}@supports not ((-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(20px))){body{--blur-bg: #ffffff}body.dark-theme{--blur-bg: #37383a}}body:has(.modal.in),body.modal-open{--blur-bg: #ffffff}body.dark-theme:has(.modal.in),body.dark-theme.modal-open{--blur-bg: #37383a}body.fps-accelerat{--blur-bg: #ffffff}body.fps-accelerat.dark-theme{--blur-bg: #37383a}body.fps-accelerat *{-webkit-backdrop-filter:unset!important;backdrop-filter:unset!important}a[href],.relates-thumb li a>.post-info,.more-posts a>span{transition:color .2s,background .3s,opacity .3s,box-shadow .3s,transform .3s}.main-shadow{transition:box-shadow .3s}.but:focus,.but:hover,.progress,.navbar-nav>li:before{transition:.3s}.navbar-top .sub-menu,.theme-popover,.hover-show .hover-show-con{opacity:0;transform:translateY(6px);transition:.4s}.navbar-top li:hover>.sub-menu,.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{opacity:1;transform:translateY(0)}.hover-show{position:relative;cursor:pointer;z-index:10}.hover-show .hover-show-con{position:absolute;visibility:hidden}.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{visibility:unset}.match-ok,.navbar-top .sub-menu li>a,.ic-close{transition:.3s}.ic-close:hover{transform:rotate(270deg);color:inherit}.social_loginbar a,.item-thumbnail img,.dosc-nav .fa{transition:.2s ease-out}.hover-zoom:hover,.hover-zoom-img:hover img,.social_loginbar .xh-social-item:hover,.social_loginbar .social-login-item:hover{transform:scale(1.05)}.hover-zoom-sm:hover,.hover-zoom-img-sm:hover img,.posts-mini:hover img,.posts-item:hover .item-thumbnail img,.posts-item.mult-thumb .thumb-items>span>img:hover{transform:scale(1.02)}.main-min-height{min-height:calc(100vh - 280px)}.main-shadow{box-shadow:0 0 10px var(--main-shadow)}.key-color{color:var(--key-color)}.focus-color{--this-color: var(--focus-color);color:var(--focus-color)}.muted-color{color:var(--muted-color)}.muted-2-color{color:var(--muted-2-color)}.muted-3-color{color:var(--muted-3-color)}.main-bg{background:var(--main-bg-color)}.placeholder{background:linear-gradient(118deg,#7979791a 25%,#79797933 37%,#7979791a 63%);opacity:1;border-radius:4px;-webkit-animation:placeholder 1.5s ease infinite!important;animation:placeholder 1.5s ease infinite!important;background-size:400% 100%!important}.placeholder.img{height:119px}.placeholder.t1{height:24px;width:80%}.placeholder.k1{height:20px;width:60%}.placeholder.k2{height:20px}.placeholder.k3{height:15px}.placeholder.s1{width:70px;height:15px;display:inline-block}@-webkit-keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@-webkit-keyframes fade{0%{opacity:0}to{opacity:1}}@keyframes fade{0%{opacity:0}to{opacity:1}}.ani.shake{-webkit-animation:shake .3s ease-in!important;animation:shake .3s ease-in!important}@-webkit-keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}@keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}.obs-animate{opacity:0;transition:opacity .6sLine truncated
@@ -354,12 +354,24 @@ if (!function_exists('jio_get_header_slider_markup')) {
$slide_class .= ' gradient-bg';
$slide_attrs .= ' data-opacity="0.2"';
}
// 叠加组件:每个背景项目可以带一段附加 HTML,显示在背景内容下方。
// 值在保存时已经过 wp_kses_post(),这里保持作者写的结构。
$overlay = is_array($slide['overlay'] ?? null) ? $slide['overlay'] : [];
$overlay_html = '';
if (trim((string) ($overlay['after_html'] ?? '')) !== '') {
$overlay_html = '<div class="slide-overlay-after">'
. wp_kses_post((string) $overlay['after_html'])
. '</div>';
}
$url = esc_url((string) ($link['url'] ?? ''));
$target = !empty($link['target']) ? ' target="_blank" rel="noopener noreferrer"' : '';
$slides_html .= '<div class="swiper-slide ' . esc_attr(trim($slide_class)) . '"' . $slide_attrs . '>' . $blur_html;
$slides_html .= $url !== '' ? '<a href="' . esc_url($url) . '"' . $target . '>' : '<span>';
$slides_html .= $background_html . $layers_html . $text_html;
$slides_html .= $url !== '' ? '</a>' : '</span>';
$slides_html .= $overlay_html;
$slides_html .= '</div>';
$slide_count++;
}
+6
View File
@@ -189,6 +189,7 @@ final class PageConfigMetaBox {
];
}
$text = is_array($slide['text'] ?? null) ? $slide['text'] : [];
$overlay = is_array($slide['overlay'] ?? null) ? $slide['overlay'] : [];
$link = is_array($slide['link'] ?? null) ? $slide['link'] : [];
$clean[] = [
'background' => esc_url_raw((string) ($slide['background'] ?? '')),
@@ -201,6 +202,10 @@ final class PageConfigMetaBox {
'text' => sanitize_text_field((string) ($link['text'] ?? '')),
],
'image_layer' => $layers,
'overlay' => [
// 叠加组件:幻灯片之上的附加 HTML,按项目独立配置。
'after_html' => wp_kses_post((string) ($overlay['after_html'] ?? '')),
],
'text' => [
'title' => wp_kses_post((string) ($text['title'] ?? '')),
'desc' => wp_kses_post((string) ($text['desc'] ?? '')),
@@ -302,6 +307,7 @@ final class PageConfigMetaBox {
$slides[] = [
'background' => $base . $image,
'link' => ['url' => home_url('/'), 'target' => '_blank'],
'overlay' => ['after_html' => ''],
'text' => ['desc' => '', 'title' => '', 'text_align' => 'left-bottom', 'parallax' => 40, 'text_size_m' => 20, 'text_size_pc' => 30],
];
}
+5 -1
View File
@@ -169,6 +169,10 @@ class SchemaPageDisplay {
self::field('background', '图片背景', 'upload', ''), self::field('background_video', '视频背景', 'upload', '', ['media_type' => 'video']), self::field('hide', '显示规则', 'radio', '', ['options' => self::options(['' => '全部显示', 'pc' => 'PC端不显示', 'm' => '移动端不显示'])]), self::field('link', '跳转链接', 'link', []),
self::field('image_layer', '叠加图层', 'group', [], ['button_title' => '添加图层', 'fields' => [self::field('image', '图层图片', 'upload', ''), self::field('free_size', '自由尺寸', 'switcher', false), self::field('align', '图层对齐', 'radio', 'center', ['dependsOn' => ['all' => [self::not_empty('image'), self::enabled('free_size')]], 'options' => self::options(['left' => '靠左显示', 'center' => '居中显示', 'right' => '靠右显示'])]), self::field('parallax', '视差滚动', 'number', 0, ['dependsOn' => self::not_empty('image'), 'min' => -200, 'max' => 200, 'step' => 5]), self::field('parallax_scale', '视差缩放', 'number', 100, ['dependsOn' => self::not_empty('image'), 'min' => 1, 'max' => 200, 'step' => 5]), self::field('parallax_opacity', '视差透明', 'number', 100, ['dependsOn' => ['all' => [self::not_empty('image'), self::not_empty('parallax')]], 'min' => 1, 'max' => 100, 'step' => 5])]]),
self::field('text', '叠加文案', 'accordion', ['title' => '', 'desc' => '', 'text_align' => 'left-bottom', 'text_size_pc' => 30, 'text_size_m' => 20, 'parallax' => 40], ['fields' => [self::field('title', '幻灯片标题', 'textarea', '', ['sanitize_type' => 'html']), self::field('desc', '幻灯片简介', 'textarea', '', ['dependsOn' => self::not_empty('title'), 'sanitize_type' => 'html']), self::field('text_align', '显示位置', 'image_select', 'left-bottom', ['dependsOn' => self::not_empty('title'), 'previewWidth' => 160, 'options' => self::text_align_options()]), self::field('text_size_pc', 'PC端字体大小', 'number', 30, ['dependsOn' => self::not_empty('title'), 'min' => 12, 'max' => 50, 'step' => 1]), self::field('text_size_m', '移动端字体大小', 'number', 20, ['dependsOn' => self::not_empty('title'), 'min' => 12, 'max' => 50, 'step' => 1]), self::field('parallax', '文案视差滚动', 'number', 40, ['dependsOn' => self::not_empty('title'), 'min' => -200, 'max' => 200, 'step' => 10])]]),
// 叠加组件:幻灯片之上的附加内容,独立于「叠加文案」,按项目逐条配置。
self::field('overlay', '叠加组件', 'accordion', ['after_html' => ''], ['fields' => [
self::field('after_html', '组件下方额外内容', 'textarea', '', ['sanitize_type' => 'html', 'rows' => 4, 'description' => '支持 HTML 代码;显示在背景内容下方,请注意代码规范与组件总高度。']),
]]),
];
}
@@ -187,7 +191,7 @@ class SchemaPageDisplay {
private static function orderby_fields(): array { return [self::field('lists', '显示排序方式', 'select', ['modified', 'views', 'like', 'comment_count'], ['multiple' => true, 'sortable' => true, 'options' => self::post_order_options(false)]), self::field('dropdown', '更多排序方式', 'switcher', false)]; }
private static function ajax_fields(string $source): array { return [self::field('lists', '按钮列表', 'async_select', [], ['source' => $source, 'multiple' => true, 'sortable' => true]), self::field('dropdown', '下拉列表', 'switcher', false), self::field('dropdown_lists', '下拉菜单列表', 'async_select', [], ['dependsOn' => self::enabled('dropdown'), 'source' => $source, 'multiple' => true, 'sortable' => true])]; }
private static function footer_image_fields(): array { return [self::field('text', '显示文字', 'text', ''), self::field('image', '显示图片', 'upload', ''), self::field('link', '点击跳转', 'link', [])]; }
private static function header_slider_defaults(): array { $img = self::asset('assets/img/'); return [['background' => $img . 'slider-bg.jpg', 'link' => ['url' => home_url('/'), 'target' => '_blank'], 'text' => ['title' => '', 'desc' => '', 'text_align' => 'left-bottom', 'parallax' => 40, 'text_size_m' => 20, 'text_size_pc' => 30]], ['background' => $img . 'user_t.jpg', 'link' => ['url' => home_url('/'), 'target' => '_blank'], 'text' => ['title' => '', 'desc' => '', 'text_align' => 'left-bottom', 'parallax' => 40, 'text_size_m' => 20, 'text_size_pc' => 30]]]; }
private static function header_slider_defaults(): array { $img = self::asset('assets/img/'); return [['background' => $img . 'slider-bg.jpg', 'link' => ['url' => home_url('/'), 'target' => '_blank'], 'overlay' => ['after_html' => ''], 'text' => ['title' => '', 'desc' => '', 'text_align' => 'left-bottom', 'parallax' => 40, 'text_size_m' => 20, 'text_size_pc' => 30]], ['background' => $img . 'user_t.jpg', 'link' => ['url' => home_url('/'), 'target' => '_blank'], 'overlay' => ['after_html' => ''], 'text' => ['title' => '', 'desc' => '', 'text_align' => 'left-bottom', 'parallax' => 40, 'text_size_m' => 20, 'text_size_pc' => 30]]]; }
private static function slider_options_defaults(): array { return ['direction' => 'horizontal', 'loop' => true, 'button' => true, 'pagination' => true, 'effect' => 'slide', 'scale_height' => false, 'auto_height' => false, 'pc_height' => 550, 'm_height' => 280, 'spacebetween' => 0, 'speed' => 0, 'autoplay' => true, 'interval' => 6]; }
private static function search_defaults(): array { return ['show_keywords' => true, 'popular_limit' => 6, 'show_input_cat' => false, 'in_cat' => '', 'more_cats' => '', 'show_type' => false, 'in_type' => 'post', 'before_html' => '<div class="em16 font-bold mb10">更优雅的Wordpress主题</div>' . "\n" . '<div>这是导航栏幻灯片多功能组件,可在后台进行配置</div>', 'after_html' => '']; }
private static function card_defaults(): array { return ['show_widget_bg' => false, 'icon_radius4' => false, 'cards' => [['icon' => 'fa fas fa-gem', 'customize_icon' => '', 'icon_class' => 'c-yellow', 'link' => [], 'title' => '图标卡片', 'desc' => '这是一个图标卡片示例'], ['icon' => 'fa fas fa-star', 'customize_icon' => '', 'icon_class' => 'c-blue-2', 'link' => [], 'title' => '原创作品', 'desc' => '这是一个图标卡片示例'], ['icon' => 'fa fas fa-fire', 'customize_icon' => '', 'icon_class' => 'jb-pink', 'link' => [], 'title' => '灵感来源<badge class="ml6 jb-yellow">NEW</badge>', 'desc' => '这是一个图标卡片示例'], ['icon' => 'fa fas fa-wrench', 'customize_icon' => '', 'icon_class' => 'jb-cyan', 'link' => [], 'title' => '系统工具 <badge class="ml6 jb-blue">GO <span class="fa fas fa-angle-right em12"></span></badge>', 'desc' => '这是一个图标卡片示例']]]; }
+1
View File
@@ -95,6 +95,7 @@ trait DisplayPageGroups {
$slides[] = [
'background' => $base . $image,
'link' => ['url' => 'https://www.legacy.com/', 'target' => '_blank'],
'overlay' => ['after_html' => ''],
'text' => ['desc' => '', 'title' => '', 'text_align' => 'left-bottom', 'parallax' => 40, 'text_size_m' => 20, 'text_size_pc' => 30],
];
}
@@ -48,6 +48,7 @@ namespace {
function get_bloginfo($show = ''): string { return 'Jio'; }
function home_url($path = ''): string { return 'https://example.test/' . ltrim((string) $path, '/'); }
function wp_kses($value, $allowed = []): string { return (string) $value; }
function wp_kses_post($value): string { return (string) $value; }
function jio_is_lazy(string $key): bool { return !empty($GLOBALS['jio_lazy']); }
function jio_get_lazy_thumb(string $size = ''): string { return 'https://example.test/placeholder.svg'; }
function jio_get_lazy_attr(string $key, string $src, string $class = '', string $lazy_src = ''): string {
@@ -64,7 +65,7 @@ namespace {
require_once dirname(__DIR__, 2) . '/inc/helpers/homepage/header-components.php';
$slides = [
['background' => 'https://example.test/slide.jpg', 'link' => ['url' => 'https://example.test/one', 'target' => '_blank', 'text' => 'One'], 'text' => ['title' => 'Title', 'desc' => 'Description'], 'image_layer' => [['image' => 'https://example.test/layer.png', 'free_size' => true, 'align' => 'right', 'parallax' => 40, 'parallax_opacity' => 80, 'parallax_scale' => 110]]],
['background' => 'https://example.test/slide.jpg', 'link' => ['url' => 'https://example.test/one', 'target' => '_blank', 'text' => 'One'], 'text' => ['title' => 'Title', 'desc' => 'Description'], 'overlay' => ['after_html' => '<div class="overlay-cta"><a href="https://example.test/cta">立即查看</a></div>'], 'image_layer' => [['image' => 'https://example.test/layer.png', 'free_size' => true, 'align' => 'right', 'parallax' => 40, 'parallax_opacity' => 80, 'parallax_scale' => 110]]],
['background_video' => 'https://example.test/background.mp4', 'background' => 'https://example.test/poster.jpg'],
['video' => 'https://example.test/content.mp4', 'background' => 'https://example.test/content-poster.jpg'],
['background' => 'https://example.test/pc-only.jpg', 'hide' => 'm'],
@@ -29,6 +29,8 @@ test('header multifunction renders reference slide, search, and card contracts o
assert.match(desktop, /header-slider-search-more text-center before/);
assert.match(desktop, /<div class="search-input">search-box<\/div>/);
assert.match(desktop, /<div class="icon-cover-card flex ac jio-panel">Card one<\/div>/);
// 叠加组件(每项 after_html)跟随所属幻灯片输出,不能落到幻灯片之外。
assert.match(desktop, /<div class="slide-overlay-after"><div class="overlay-cta"><a href="https:\/\/example\.test\/cta">立即查看<\/a><\/div><\/div><\/div>/);
assert.doesNotMatch(mobile, /background\.mp4/);
assert.match(mobile, /data-src="https:\/\/example\.test\/mobile-only\.jpg"/);
@@ -56,8 +58,38 @@ test('header multifunction renders reference slide, search, and card contracts o
assert.doesNotMatch(single, /swiper-pagination|swiper-button-prev/);
});
test('header card background setting is editable, saved, and defaults off', () => {
test('each background item exposes an overlay group with after_html', () => {
const schema = fs.readFileSync(path.join(root, 'src/Admin/Schema/SchemaPageDisplay.php'), 'utf8');
const metaBox = fs.readFileSync(path.join(root, 'src/Admin/PageConfigMetaBox.php'), 'utf8');
const helper = fs.readFileSync(path.join(root, 'inc/helpers/homepage/header-components.php'), 'utf8');
const css = fs.readFileSync(path.join(root, 'assets/css/main.css'), 'utf8');
// 后台:「背景内容」每个项目里都有「叠加组件」手风琴,含 HTML 输入框。
const itemFields = schema.slice(
schema.indexOf('private static function slider_item_fields'),
schema.indexOf('private static function slider_option_fields'),
);
assert.match(itemFields, /field\('overlay', '叠加组件', 'accordion'/);
assert.match(itemFields, /field\('after_html', '组件下方额外内容', 'textarea', '', \['sanitize_type' => 'html'/);
// 默认值里带上空 overlay,新加的项目不必手动展开即可保存。
assert.match(schema, /'overlay' => \['after_html' => ''\]/);
// 页面独立配置(page_config 元数据)同样落盘 after_html。
assert.match(metaBox, /\$overlay = is_array\(\$slide\['overlay'\] \?\? null\) \? \$slide\['overlay'\] : \[\]/);
assert.match(metaBox, /'after_html' => wp_kses_post\(\(string\) \(\$overlay\['after_html'\] \?\? ''\)\)/);
assert.match(metaBox, /'overlay' => \['after_html' => ''\]/);
// 前端:内容挂在所属幻灯片内,并作为覆盖层渲染。
assert.match(helper, /\$overlay_html = '<div class="slide-overlay-after">'/);
assert.match(helper, /\$slides_html \.= \$overlay_html;/);
assert.match(css, /\.slide-overlay-after \{[^}]*position: absolute/);
assert.match(css, /\.slide-overlay-after \{[^}]*z-index: 3/);
// 覆盖层不能挡住背景上的其它交互,只有其中的可交互元素接收点击。
assert.match(css, /\.slide-overlay-after \{[^}]*pointer-events: none/);
assert.match(css, /\.slide-overlay-after a,[\s\S]*?pointer-events: auto/);
});
test('header card background setting is editable, saved, and defaults off', () => { const schema = fs.readFileSync(path.join(root, 'src/Admin/Schema/SchemaPageDisplay.php'), 'utf8');
const settings = fs.readFileSync(path.join(root, 'src/Settings/DisplayPageGroups.php'), 'utf8');
const metaBox = fs.readFileSync(path.join(root, 'src/Admin/PageConfigMetaBox.php'), 'utf8');
@@ -469,6 +469,9 @@ test('save contract rejects invalid input and round-trips canonical nested value
assert.deepEqual(slide.link, { url: 'https://example.test/slide', target: '_blank', text: 'Go' })
assert.equal(slide.text.title, '<strong>Safe</strong>')
assert.equal(Object.hasOwn(slide.text, 'unknown'), false)
// 叠加组件按项目保存,未知键丢弃,脚本被过滤。
assert.equal(slide.overlay.after_html, '<div class="cta">Below</div>')
assert.equal(Object.hasOwn(slide.overlay, 'unknown'), false)
assert.equal(data.footer_clean.fcode_t1_code, '<strong>Allowed</strong>')
assert.equal(data.footer_clean.footer_contact_weibo, '')
@@ -183,6 +183,7 @@ namespace {
'unknown' => 'drop-me',
'link' => ['url' => 'https://example.test/slide', 'target' => '_blank', 'text' => '<b>Go</b>', 'unknown' => 'drop-me'],
'text' => ['title' => '<strong>Safe</strong><script>bad()</script>', 'desc' => '<em>Copy</em>', 'unknown' => 'drop-me'],
'overlay' => ['after_html' => '<div class="cta">Below</div><script>bad()</script>', 'unknown' => 'drop-me'],
]],
'header_slider_card_option' => ['show_widget_bg' => true, 'cards' => []],
];