Files
wordpress-theme-aksrt/inc/helpers/aksrt-slider.php
T
kate522andClaude Fable 5 57ff405691 refactor(color): unify color sanitizing and picking behind shared components
- Add AKSRT\Core\Color::sanitize($value, $allow_alpha) as the single PHP
  color sanitizer; replace sanitize_hex_color calls and per-class regexes
  (FloatButtons, FieldSchema, ThemeSearch, icon cards, TicketStatusService)
- SchemaProvider / ControllerBase sanitize `color` fields as colors;
  alpha kept only when the schema sets showAlpha
- Split SchemaProvider OAuth validation and option matching into sibling
  traits to stay under the 1000-line limit
- Add AksColorPicker.vue + utils/color.js mirroring the PHP rules; AksField,
  FloatBtnCommonFields and TicketStatusPanel use it
- Widget background color input now uses wpColorPicker
- Remove dead AksCompositeField dimensions/spacing/background branches and
  the unused Gutenberg ColorPicker var
- Add color-unification contract test; update harness requires and tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-28 20:47:41 +08:00

240 lines
9.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* AKSRT Slider Helper — 轮播渲染函数
*
* 对标 legacy 的 legacy(),提供服务端轮播 HTML 生成。
* 支持 data-* 属性驱动配置、视差、懒加载、缩略图分页。
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
/**
* 渲染 Swiper 轮播
*
* @param array $args {
* @type array $slides 幻灯片数据数组
* @type string $effect 动画效果: slide, fade, cube, coverflow, flip (默认 slide)
* @type bool $loop 是否循环 (默认 true)
* @type bool $autoplay 是否自动播放 (默认 true)
* @type int $interval 自动播放间隔 ms (默认 5000)
* @type int $speed 动画速度 ms (0=自动计算)
* @type string $direction 方向: horizontal, vertical (默认 horizontal)
* @type int $spaceBetween 幻灯片间距 px (默认 0)
* @type string $pagination_type 分页器类型: bullets, fraction, progressbar, thumbnail (默认 bullets)
* @type string $pc_height PC 端高度 CSS 值 (默认 auto)
* @type string $m_height 移动端高度 CSS 值 (默认 auto)
* @type bool $scale_height 是否使用比例高度 (默认 false)
* @type float $scale_ratio 比例值 (默认 0.45 = 45%)
* @type string $scale_extra 比例高度附加量,如 '50px'(用于封面标题预留,默认空)
* @type string $wrapper_class 额外 wrapper 类
* @type string $slider_class 额外 slider 类
* @type bool $show_nav 是否显示导航箭头 (默认 true)
* @type bool $show_pagination 是否显示分页器 (默认 true)
* }
*
* slide 数据格式:
* [
* 'image' => 'https://...',
* 'link' => 'https://...',
* 'title' => '标题',
* 'desc' => '描述',
* 'alt' => '图片 alt',
* 'bg_color' => '#f0f0f0', // 背景色
* 'target' => '_blank',
* ]
*
* @return string HTML
*/
function aksrt_render_slider(array $args = []): string {
$defaults = [
'slides' => [],
'effect' => 'slide',
'loop' => true,
'autoplay' => true,
'interval' => 5000,
'speed' => 0,
'direction' => 'horizontal',
'spaceBetween' => 0,
'pagination_type' => 'bullets',
'pc_height' => 'auto',
'm_height' => 'auto',
'scale_height' => false,
'scale_ratio' => 0.45,
'scale_extra' => '',
'wrapper_class' => '',
'slider_class' => '',
'show_nav' => true,
'show_pagination' => true,
// Optional absolute overlay HTML (e.g. single cover title/meta). Not escaped.
'overlay_html' => '',
];
$args = wp_parse_args($args, $defaults);
$slides = $args['slides'];
if (empty($slides) || !is_array($slides)) {
return '';
}
// 构建 data 属性
$data_attrs = [];
$data_attrs[] = 'data-effect="' . esc_attr($args['effect']) . '"';
if ($args['loop']) $data_attrs[] = 'data-loop';
if ($args['autoplay']) {
$data_attrs[] = 'data-autoplay';
$data_attrs[] = 'data-interval="' . esc_attr((string) $args['interval']) . '"';
}
if ($args['speed'] > 0) $data_attrs[] = 'data-speed="' . esc_attr((string) $args['speed']) . '"';
if ($args['direction'] !== 'horizontal') $data_attrs[] = 'data-direction="' . esc_attr($args['direction']) . '"';
if ($args['spaceBetween'] > 0) $data_attrs[] = 'data-spaceBetween="' . esc_attr((string) $args['spaceBetween']) . '"';
if ($args['pagination_type'] !== 'bullets') $data_attrs[] = 'data-pagination-type="' . esc_attr($args['pagination_type']) . '"';
// 高度样式
$height_style = '';
$scale_padding = '';
if ($args['scale_height']) {
// padding-bottom percentage for ratio box (legacy graphic cover style).
// 内层 .new-swiper 绝对定位铺满 wrapper,wrapper 决定盒子高度;
// pc/m-height 100% 让 .swiper-wrapper/.swiper-slide 铺满盒子,
// 使 object-fit: cover 在确定的盒内居中裁切(对齐单图封面)。
$ratio_pct = max(10, min(100, (float) $args['scale_ratio'] * 100));
$scale_padding = $ratio_pct . '%';
$scale_extra = trim((string) $args['scale_extra']);
if ($scale_extra !== '') {
$scale_padding = 'calc(' . $scale_padding . ' + ' . $scale_extra . ')';
}
$height_style = '--pc-height:100%;--m-height:100%;';
} else {
$height_style = sprintf('--pc-height:%s;--m-height:%s;', esc_attr($args['pc_height']), esc_attr($args['m_height']));
}
// CSS 类
$slider_classes = ['new-swiper', 'slide-index'];
if ($args['slider_class']) $slider_classes[] = $args['slider_class'];
$slider_class_str = implode(' ', $slider_classes);
$wrapper_classes = ['aksrt-slider-wrapper'];
if ($args['wrapper_class']) $wrapper_classes[] = $args['wrapper_class'];
if ($args['scale_height']) $wrapper_classes[] = 'aksrt-slider-wrapper--scale';
if ((string) $args['overlay_html'] !== '') $wrapper_classes[] = 'aksrt-slider-wrapper--has-overlay';
$wrapper_class_str = implode(' ', $wrapper_classes);
// 渲染 HTML
$html = '<div class="' . esc_attr($wrapper_class_str) . '"' . ($args['scale_height'] ? ' style="position:relative;padding-bottom:' . esc_attr($scale_padding) . ';"' : '') . '>';
$html .= '<div class="' . esc_attr($slider_class_str) . '" ' . implode(' ', $data_attrs) . ' style="' . esc_attr($height_style . ($args['scale_height'] ? 'position:absolute;inset:0;width:100%;height:100%;' : '')) . '">';
$html .= '<div class="swiper-wrapper">';
foreach ($slides as $slide) {
$image = esc_url($slide['image'] ?? '');
$link = esc_url($slide['link'] ?? '');
$title = sanitize_text_field($slide['title'] ?? '');
$desc = sanitize_text_field($slide['desc'] ?? '');
$alt = sanitize_text_field($slide['alt'] ?? $title);
$bg_color = \AKSRT\Core\Color::sanitize($slide['bg_color'] ?? '');
$target = ($slide['target'] ?? '') === '_blank' ? '_blank' : '_self';
$slide_style = $bg_color ? 'background-color:' . esc_attr($bg_color) . ';' : '';
$html .= '<div class="swiper-slide"' . ($slide_style ? ' style="' . esc_attr($slide_style) . '"' : '') . '>';
if ($link) {
$html .= '<a href="' . esc_url($link) . '" target="' . esc_attr($target) . '">';
}
if ($image) {
$html .= '<img' . aksrt_get_lazy_attr('lazy_sider', (string) $image, 'fit-cover', aksrt_get_lazy_thumb('lg')) . ' alt="' . esc_attr($alt) . '">';
}
if ($link) {
$html .= '</a>';
}
// 文字覆盖层
if ($title || $desc) {
$html .= '<div class="aksrt-slide-text">';
if ($title) $html .= '<div class="aksrt-slide-text__title">' . esc_html($title) . '</div>';
if ($desc) $html .= '<div class="aksrt-slide-text__desc">' . esc_html($desc) . '</div>';
$html .= '</div>';
}
$html .= '</div>';
}
$html .= '</div>'; // .swiper-wrapper
if ($args['show_nav']) {
$html .= '<div class="swiper-button-prev"></div>';
$html .= '<div class="swiper-button-next"></div>';
}
if ($args['show_pagination']) {
$html .= '<div class="swiper-pagination"></div>';
}
$html .= '</div>'; // .new-swiper
// Cover title/meta overlay (trusted HTML from single cover renderer).
if ((string) $args['overlay_html'] !== '') {
$html .= '<div class="aksrt-slider-overlay">' . $args['overlay_html'] . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$html .= '</div>'; // .aksrt-slider-wrapper
return $html;
}
/**
* 渲染水平滚动器(Tab 导航、横向内容列表)
*
* @param array $args {
* @type array $items 内容数组
* @type string $wrapper_class 额外 wrapper 类
* @type bool $autoplay 是否自动播放
* @type int $interval 自动播放间隔
* @type string $render_item 自定义渲染回调 (callable)
* }
* @return string HTML
*/
function aksrt_render_scroll_slider(array $args = []): string {
$defaults = [
'items' => [],
'wrapper_class' => '',
'autoplay' => false,
'interval' => 4000,
'render_item' => null,
];
$args = wp_parse_args($args, $defaults);
$items = $args['items'];
if (empty($items)) return '';
$data_attrs = ['class="swiper-scroll' . ($args['wrapper_class'] ? ' ' . esc_attr($args['wrapper_class']) : '') . '"'];
if ($args['autoplay']) {
$data_attrs[] = 'data-autoplay';
$data_attrs[] = 'data-interval="' . esc_attr((string) $args['interval']) . '"';
}
$html = '<div ' . implode(' ', $data_attrs) . '>';
$html .= '<div class="swiper-wrapper">';
foreach ($items as $i => $item) {
$html .= '<div class="swiper-slide">';
if (is_callable($args['render_item'])) {
$html .= call_user_func($args['render_item'], $item, $i);
} else {
$html .= '<div class="aksrt-scroll-item">' . esc_html($item['label'] ?? '') . '</div>';
}
$html .= '</div>';
}
$html .= '</div>';
$html .= '<div class="swiper-button-prev"></div>';
$html .= '<div class="swiper-button-next"></div>';
$html .= '</div>';
return $html;
}