Files
wordpress-theme-aksrt/src/AKSRT/Widgets/WidgetSlider.php
T
kate522 4ce3681900 feat(widgets): AKSRT image slider widget
Lightweight multi-slide carousel with captions, dots, arrows, and CSS
bundled via widgets.css import; registered in widgets-register.
2026-07-18 23:54:04 +08:00

242 lines
11 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
/**
* Simple image slider widget (zibll widget_ui_slider lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetSlider extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_slider',
esc_html__('AKSRT 幻灯片', 'aksrt'),
[
'description' => esc_html__('多图轮播,支持链接与标题。', 'aksrt'),
'classname' => 'widget_aksrt_slider',
]
);
}
public function widget($args, $instance): void {
$slides = $this->normalize_slides($instance['slides'] ?? []);
if (!$slides) {
return;
}
$interval = max(2, min(20, (int) ($instance['interval'] ?? 4)));
$loop = !empty($instance['loop']);
$dots = !empty($instance['pagination']);
$nav = !empty($instance['button']);
$blank = !empty($instance['blank']);
$height = max(80, min(800, (int) ($instance['height'] ?? 220)));
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$uid = 'aksrt-slider-' . esc_attr($this->id);
printf(
'<div class="aksrt-widget-slider" id="%s" data-interval="%d" data-loop="%s" style="--aksrt-slider-h:%dpx">',
$uid,
$interval * 1000,
$loop ? '1' : '0',
$height
);
echo '<div class="aksrt-widget-slider__track">';
foreach ($slides as $i => $slide) {
$active = $i === 0 ? ' is-active' : '';
echo '<div class="aksrt-widget-slider__slide' . esc_attr($active) . '">';
$img = '<img src="' . esc_url($slide['image']) . '" alt="' . esc_attr($slide['title']) . '" loading="lazy" />';
if ($slide['href'] !== '') {
printf(
'<a href="%s"%s class="aksrt-widget-slider__link">%s</a>',
esc_url($slide['href']),
$blank ? ' target="_blank" rel="noopener noreferrer"' : '',
$img // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
} else {
echo $img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($slide['title'] !== '' || $slide['desc'] !== '') {
echo '<div class="aksrt-widget-slider__caption">';
if ($slide['title'] !== '') {
echo '<strong>' . esc_html($slide['title']) . '</strong>';
}
if ($slide['desc'] !== '') {
echo '<span>' . esc_html($slide['desc']) . '</span>';
}
echo '</div>';
}
echo '</div>';
}
echo '</div>';
if ($nav) {
echo '<button type="button" class="aksrt-widget-slider__prev" aria-label="' . esc_attr__('上一张', 'aksrt') . '">‹</button>';
echo '<button type="button" class="aksrt-widget-slider__next" aria-label="' . esc_attr__('下一张', 'aksrt') . '">›</button>';
}
if ($dots) {
echo '<div class="aksrt-widget-slider__dots" role="tablist">';
foreach ($slides as $i => $slide) {
printf(
'<button type="button" class="aksrt-widget-slider__dot%s" data-index="%d" aria-label="%s"></button>',
$i === 0 ? ' is-active' : '',
(int) $i,
esc_attr(sprintf(__('第 %d 张', 'aksrt'), $i + 1))
);
}
echo '</div>';
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
// Inline init once per widget (no extra enqueue dependency).
?>
<script>
(function(){
var root=document.getElementById(<?php echo wp_json_encode($uid); ?>);
if(!root||root._aksrtSlider)return; root._aksrtSlider=1;
var slides=[].slice.call(root.querySelectorAll('.aksrt-widget-slider__slide'));
var dots=[].slice.call(root.querySelectorAll('.aksrt-widget-slider__dot'));
var i=0, timer=null, ms=parseInt(root.getAttribute('data-interval')||'4000',10)||4000;
var loop=root.getAttribute('data-loop')==='1';
function go(n){
if(!slides.length)return;
i=(n+slides.length)%slides.length;
slides.forEach(function(s,idx){s.classList.toggle('is-active',idx===i);});
dots.forEach(function(d,idx){d.classList.toggle('is-active',idx===i);});
}
function next(){ if(!loop && i>=slides.length-1){go(0);return;} go(i+1); }
function start(){ stop(); if(slides.length>1) timer=setInterval(next, ms); }
function stop(){ if(timer){clearInterval(timer);timer=null;} }
var prev=root.querySelector('.aksrt-widget-slider__prev');
var nxt=root.querySelector('.aksrt-widget-slider__next');
if(prev) prev.addEventListener('click', function(){ go(i-1); start(); });
if(nxt) nxt.addEventListener('click', function(){ go(i+1); start(); });
dots.forEach(function(d){ d.addEventListener('click', function(){ go(parseInt(d.getAttribute('data-index')||'0',10)); start(); }); });
root.addEventListener('mouseenter', stop);
root.addEventListener('mouseleave', start);
start();
})();
</script>
<?php
}
public function form($instance): void {
$title = (string) ($instance['title'] ?? '');
$interval = (int) ($instance['interval'] ?? 4);
$height = (int) ($instance['height'] ?? 220);
$slides = $this->normalize_slides($instance['slides'] ?? []);
if (!$slides) {
$slides = [
['image' => '', 'href' => '', 'title' => '', 'desc' => ''],
['image' => '', 'href' => '', 'title' => '', 'desc' => ''],
];
}
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($title); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('切换间隔(秒)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="2" max="20" name="<?php echo esc_attr($this->get_field_name('interval')); ?>" value="<?php echo esc_attr((string) $interval); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('高度(px)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="80" max="800" name="<?php echo esc_attr($this->get_field_name('height')); ?>" value="<?php echo esc_attr((string) $height); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('loop')); ?>" value="1" <?php checked(!empty($instance['loop']) || !isset($instance['loop'])); ?>> <?php esc_html_e('循环播放', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('button')); ?>" value="1" <?php checked(!empty($instance['button']) || !isset($instance['button'])); ?>> <?php esc_html_e('翻页按钮', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('pagination')); ?>" value="1" <?php checked(!empty($instance['pagination']) || !isset($instance['pagination'])); ?>> <?php esc_html_e('指示点', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!empty($instance['blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<?php foreach ($slides as $i => $slide) : ?>
<p style="border-top:1px solid #ddd;padding-top:8px;">
<strong><?php echo esc_html(sprintf(__('幻灯片 %d', 'aksrt'), $i + 1)); ?></strong><br>
<label><?php esc_html_e('图片 URL', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][image]"); ?>" value="<?php echo esc_attr($slide['image']); ?>">
</label>
<label><?php esc_html_e('链接', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][href]"); ?>" value="<?php echo esc_attr($slide['href']); ?>">
</label>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][title]"); ?>" value="<?php echo esc_attr($slide['title']); ?>">
</label>
<label><?php esc_html_e('简介', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][desc]"); ?>" value="<?php echo esc_attr($slide['desc']); ?>">
</label>
</p>
<?php endforeach; ?>
<p class="description"><?php esc_html_e('保存后可继续编辑;需要更多张可先填满再在下次编辑时追加(当前表单固定 2–6 槽)。', 'aksrt'); ?></p>
<?php
// Ensure at least 4 slots for editing convenience.
$extra = max(0, 4 - count($slides));
for ($j = 0; $j < $extra; $j++) {
$i = count($slides) + $j;
?>
<p style="border-top:1px dashed #eee;padding-top:8px;">
<strong><?php echo esc_html(sprintf(__('空槽 %d', 'aksrt'), $i + 1)); ?></strong>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][image]"); ?>" placeholder="<?php esc_attr_e('图片 URL', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][href]"); ?>" placeholder="<?php esc_attr_e('链接', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][title]"); ?>" placeholder="<?php esc_attr_e('标题', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][desc]"); ?>" placeholder="<?php esc_attr_e('简介', 'aksrt'); ?>">
</p>
<?php
}
}
public function update($new_instance, $old_instance): array {
$slides = $this->normalize_slides($new_instance['slides'] ?? []);
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'interval' => max(2, min(20, (int) ($new_instance['interval'] ?? 4))),
'height' => max(80, min(800, (int) ($new_instance['height'] ?? 220))),
'loop' => !empty($new_instance['loop']) ? 1 : 0,
'button' => !empty($new_instance['button']) ? 1 : 0,
'pagination' => !empty($new_instance['pagination']) ? 1 : 0,
'blank' => !empty($new_instance['blank']) ? 1 : 0,
'slides' => $slides,
];
}
/**
* @param mixed $raw
* @return array<int, array{image:string,href:string,title:string,desc:string}>
*/
private function normalize_slides($raw): array {
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$image = esc_url_raw(trim((string) ($row['image'] ?? $row['link'] ?? '')));
if ($image === '') {
continue;
}
$out[] = [
'image' => $image,
'href' => esc_url_raw(trim((string) ($row['href'] ?? ''))),
'title' => sanitize_text_field((string) ($row['title'] ?? '')),
'desc' => sanitize_text_field((string) ($row['desc'] ?? $row['dec'] ?? '')),
];
if (count($out) >= 8) {
break;
}
}
return $out;
}
}