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( + '
'; + 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' => ''], + ]; + } + ?> ++ +
++ +
++ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + " 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