diff --git a/assets/css/frontend/widget-slider.css b/assets/css/frontend/widget-slider.css new file mode 100644 index 00000000..7e64fbec --- /dev/null +++ b/assets/css/frontend/widget-slider.css @@ -0,0 +1,93 @@ +/** + * Widget slider (AKSRT 幻灯片). + * @package AKSRT + */ +.aksrt-widget-slider { + position: relative; + overflow: hidden; + border-radius: 8px; + background: var(--surface-2, #f3f4f6); +} +.aksrt-widget-slider__track { + position: relative; + width: 100%; + height: var(--aksrt-slider-h, 220px); +} +.aksrt-widget-slider__slide { + position: absolute; + inset: 0; + opacity: 0; + pointer-events: none; + transition: opacity .35s ease; +} +.aksrt-widget-slider__slide.is-active { + opacity: 1; + pointer-events: auto; + z-index: 1; +} +.aksrt-widget-slider__slide img, +.aksrt-widget-slider__link { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} +.aksrt-widget-slider__caption { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: 10px 12px; + background: linear-gradient(transparent, rgba(0,0,0,.55)); + color: #fff; + display: flex; + flex-direction: column; + gap: 2px; + font-size: 13px; +} +.aksrt-widget-slider__caption strong { + font-size: 14px; +} +.aksrt-widget-slider__prev, +.aksrt-widget-slider__next { + position: absolute; + top: 50%; + transform: translateY(-50%); + z-index: 2; + width: 28px; + height: 28px; + border: 0; + border-radius: 50%; + background: rgba(0,0,0,.35); + color: #fff; + cursor: pointer; + line-height: 28px; + text-align: center; + padding: 0; +} +.aksrt-widget-slider__prev { left: 8px; } +.aksrt-widget-slider__next { right: 8px; } +.aksrt-widget-slider__dots { + position: absolute; + left: 0; + right: 0; + bottom: 8px; + z-index: 2; + display: flex; + justify-content: center; + gap: 6px; + pointer-events: none; +} +.aksrt-widget-slider__dot { + pointer-events: auto; + width: 7px; + height: 7px; + border-radius: 50%; + border: 0; + padding: 0; + background: rgba(255,255,255,.45); + cursor: pointer; +} +.aksrt-widget-slider__dot.is-active { + background: #fff; +} diff --git a/assets/css/frontend/widgets.css b/assets/css/frontend/widgets.css index 78107132..ed3d1caf 100644 --- a/assets/css/frontend/widgets.css +++ b/assets/css/frontend/widgets.css @@ -2,3 +2,4 @@ @import url("widgets/part-2.css"); @import url("widgets/part-3.css"); @import url("widgets/part-4.css"); +@import url("widget-slider.css"); diff --git a/inc/modules/widgets-register.php b/inc/modules/widgets-register.php index e068692a..65fbb549 100644 --- a/inc/modules/widgets-register.php +++ b/inc/modules/widgets-register.php @@ -53,6 +53,9 @@ function aksrt_register_widgets() { // 文章目录树(zibll posts_navs) register_widget('\AKSRT\Widgets\WidgetPostsNavs'); + // 幻灯片 + register_widget('\AKSRT\Widgets\WidgetSlider'); + // Forum widgets if (class_exists('\AKSRT\Widgets\WidgetForumBoardList')) { register_widget('\AKSRT\Widgets\WidgetForumBoardList'); diff --git a/src/AKSRT/Widgets/WidgetSlider.php b/src/AKSRT/Widgets/WidgetSlider.php new file mode 100644 index 00000000..fa9ad2b6 --- /dev/null +++ b/src/AKSRT/Widgets/WidgetSlider.php @@ -0,0 +1,241 @@ + 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( + '
', + $uid, + $interval * 1000, + $loop ? '1' : '0', + $height + ); + echo '
'; + foreach ($slides as $i => $slide) { + $active = $i === 0 ? ' is-active' : ''; + echo '
'; + $img = '' . esc_attr($slide['title']) . ''; + if ($slide['href'] !== '') { + printf( + '%s', + 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 '
'; + if ($slide['title'] !== '') { + echo '' . esc_html($slide['title']) . ''; + } + if ($slide['desc'] !== '') { + echo '' . esc_html($slide['desc']) . ''; + } + echo '
'; + } + echo '
'; + } + echo '
'; + if ($nav) { + echo ''; + echo ''; + } + if ($dots) { + echo '
'; + foreach ($slides as $i => $slide) { + printf( + '', + $i === 0 ? ' is-active' : '', + (int) $i, + esc_attr(sprintf(__('第 %d 张', 'aksrt'), $i + 1)) + ); + } + echo '
'; + } + echo '
'; + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + + // Inline init once per widget (no extra enqueue dependency). + ?> + + normalize_slides($instance['slides'] ?? []); + if (!$slides) { + $slides = [ + ['image' => '', 'href' => '', 'title' => '', 'desc' => ''], + ['image' => '', 'href' => '', 'title' => '', 'desc' => ''], + ]; + } + ?> +

+ +

+

+ +

+

+ +

+

+
+
+
+ +

+ $slide) : ?> +

+
+ + + + +

+ +

+ +

+ + " placeholder=""> + " placeholder=""> + " placeholder=""> + " placeholder=""> +

+ 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 + */ + 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; + } +}