对小工具进行了一些修复

This commit is contained in:
2026-08-26 08:41:24 +08:00
parent e877ceb560
commit bb9413cba6
15 changed files with 463 additions and 136 deletions
+7 -1
View File
@@ -13,7 +13,7 @@ final class FieldSchema {
public static function supported_types(): array {
return [
'text', 'textarea', 'number', 'spinner', 'slider', 'switcher', 'radio',
'button_set', 'select', 'checkbox', 'color', 'palette', 'icon', 'upload',
'button_set', 'select', 'checkbox', 'color', 'palette', 'image_select', 'icon', 'upload',
'link', 'spacing', 'background', 'group', 'fieldset', 'accordion',
'content', 'subheading', 'submessage',
];
@@ -59,6 +59,7 @@ final class FieldSchema {
case 'button_set':
case 'select':
case 'palette':
case 'image_select':
return self::choice($field, $value);
case 'checkbox':
return self::checkbox($field, $value);
@@ -86,6 +87,11 @@ final class FieldSchema {
if (($field['sanitize'] ?? '') === 'ids') {
return implode(',', array_values(array_unique(array_filter(array_map('absint', preg_split('/[,,\s]+/', (string) $value))))));
}
if (($field['sanitize'] ?? '') === 'reference_ids') {
$tokens = preg_split('/[,,\s]+/', trim((string) $value), -1, PREG_SPLIT_NO_EMPTY);
$tokens = array_map(static fn($token): string => preg_match('/^-?\d+$/', (string) $token) ? (string) (int) $token : '', $tokens ?: []);
return implode(',', array_values(array_unique(array_filter($tokens, static fn($token): bool => $token !== ''))));
}
return !empty($field['allow_html']) ? wp_kses_post((string) $value) : sanitize_text_field((string) $value);
}
}
+20 -7
View File
@@ -51,6 +51,7 @@ final class FormRenderer {
case 'radio':
case 'button_set':
case 'palette':
case 'image_select':
self::radios($field_id, $field_name, (array) ($field['options'] ?? []), (string) $value, $type);
break;
case 'select':
@@ -65,10 +66,11 @@ final class FormRenderer {
self::number($field_id, $field_name, $field, $value, $type);
break;
case 'textarea':
echo '<textarea class="widefat" rows="4" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_name) . '">' . esc_textarea((string) $value) . '</textarea>';
$rows = max(1, (int) ($field['attributes']['rows'] ?? 4));
echo '<textarea class="widefat" rows="' . $rows . '" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_name) . '">' . esc_textarea((string) $value) . '</textarea>';
break;
case 'upload':
self::upload($field_id, $field_name, (string) $value);
self::upload($field_id, $field_name, (string) $value, (string) ($field['library'] ?? ''));
break;
case 'color':
echo '<input class="widefat aksrt-widget-color" type="text" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_name) . '" value="' . esc_attr((string) $value) . '">';
@@ -113,7 +115,13 @@ final class FormRenderer {
echo '<div class="aksrt-widget-choices aksrt-widget-choices--' . esc_attr($type) . '">';
foreach ($options as $key => $label) {
$option_id = $id . '-' . sanitize_html_class((string) $key);
echo '<label for="' . esc_attr($option_id) . '"><input type="radio" id="' . esc_attr($option_id) . '" name="' . esc_attr($name) . '" value="' . esc_attr((string) $key) . '" ' . checked($value, (string) $key, false) . '><span>' . esc_html((string) $label) . '</span></label>';
$image = is_array($label) ? (string) ($label['image'] ?? $label['url'] ?? '') : (string) $label;
$text = is_array($label) ? (string) ($label['label'] ?? $label['title'] ?? $key) : (string) $label;
if ($type === 'image_select' && $text === $image) $text = (string) $key;
$body = $type === 'image_select' && filter_var($image, FILTER_VALIDATE_URL)
? '<img src="' . esc_url($image) . '" alt="' . esc_attr($text) . '"><small>' . esc_html($text) . '</small>'
: esc_html($text);
echo '<label for="' . esc_attr($option_id) . '"><input type="radio" id="' . esc_attr($option_id) . '" name="' . esc_attr($name) . '" value="' . esc_attr((string) $key) . '" ' . checked($value, (string) $key, false) . '><span>' . $body . '</span></label>';
}
echo '</div>';
}
@@ -148,13 +156,14 @@ final class FormRenderer {
echo '<div class="aksrt-widget-number"><input class="widefat" type="' . esc_attr($input_type) . '" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="' . esc_attr((string) $value) . '"' . $min . $max . $step . '><output>' . esc_html((string) $value) . '</output></div>';
}
private static function upload(string $id, string $name, string $value): void {
echo '<div class="aksrt-widget-upload">';
private static function upload(string $id, string $name, string $value, string $library = ''): void {
$library = in_array($library, ['image', 'video'], true) ? $library : '';
echo '<div class="aksrt-widget-upload" data-media-library="' . esc_attr($library) . '">';
if ($value !== '') {
echo '<img src="' . esc_url($value) . '" alt="">';
}
echo '<input class="widefat" type="url" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="' . esc_attr($value) . '">';
echo '<button type="button" class="button aksrt-widget-upload__choose">' . esc_html__('选择媒体', 'aksrt') . '</button> <button type="button" class="button-link-delete aksrt-widget-upload__remove">' . esc_html__('移除', 'aksrt') . '</button></div>';
echo '<button type="button" class="button aksrt-widget-upload__choose">' . esc_html($library === 'video' ? __('选择视频', 'aksrt') : __('选择图片', 'aksrt')) . '</button> <button type="button" class="button-link-delete aksrt-widget-upload__remove">' . esc_html__('移除', 'aksrt') . '</button></div>';
}
private static function link(SchemaWidget $widget, string $key, array $value): void {
@@ -207,7 +216,11 @@ final class FormRenderer {
}
private static function group_row(SchemaWidget $widget, array $field, array $row, string $key, string $index): void {
echo '<div class="aksrt-widget-repeater__row"><div class="aksrt-widget-repeater__bar"><span class="dashicons dashicons-move" aria-hidden="true"></span><strong>' . esc_html__('项目', 'aksrt') . '</strong><button type="button" class="button-link-delete aksrt-widget-repeater__remove">' . esc_html__('删除', 'aksrt') . '</button></div><div class="aksrt-widget-repeater__fields">';
$prefix = (string) ($field['item_label'] ?? $field['label'] ?? __('项目', 'aksrt'));
$number = ctype_digit($index) ? ((int) $index + 1) : 0;
$title = $prefix . ($number ? ' ' . $number : '');
if (!empty($row['title'])) $title .= ':' . strip_tags((string) $row['title']);
echo '<div class="aksrt-widget-repeater__row" data-row-index="' . esc_attr(ctype_digit($index) ? $index : '0') . '"><div class="aksrt-widget-repeater__bar"><span class="dashicons dashicons-move" aria-hidden="true"></span><strong>' . esc_html($title) . '</strong><button type="button" class="button-link-delete aksrt-widget-repeater__remove">' . esc_html__('删除', 'aksrt') . '</button></div><div class="aksrt-widget-repeater__fields">';
foreach ((array) ($field['fields'] ?? []) as $child) {
$child_id = (string) ($child['id'] ?? '');
self::field($widget, $child, $row[$child_id] ?? ($child['default'] ?? ''), $key . '[' . $index . ']');
+26 -2
View File
@@ -23,6 +23,13 @@ final class FrontendRenderer {
}
AssetManager::enqueue_for($definition);
if (in_array($definition->slug(), ['tab_posts', 'slider', 'slider_media_combo'], true)) {
echo (string) ($args['before_widget'] ?? '');
echo self::reference_title($instance);
echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo (string) ($args['after_widget'] ?? '');
return;
}
$classes = ['aksrt-widget', 'aksrt-panel-wrap', 'aksrt-widget--' . $definition->slug(), 'aksrt-widget--' . $definition->size()];
if ($definition->size() === 'sidebar' && $definition->slug() !== 'user_card') {
$classes[] = 'aksrt-panel';
@@ -62,7 +69,7 @@ final class FrontendRenderer {
if ($title === '') {
return '';
}
$safe_title = esc_html($title);
$safe_title = wp_kses_post($title);
$highlight = trim((string) ($instance['title_highlight'] ?? ''));
if ($highlight !== '') {
$safe_highlight = esc_html($highlight);
@@ -70,7 +77,7 @@ final class FrontendRenderer {
}
$html = '<header class="aksrt-widget__header' . (($instance['title_style'] ?? '') === 'big' ? ' aksrt-widget__header--big' : '') . '"><div><h2 class="aksrt-widget__title">' . nl2br($safe_title) . '</h2>';
if (!empty($instance['subtitle'])) {
$html .= '<p class="aksrt-widget__subtitle">' . nl2br(esc_html((string) $instance['subtitle'])) . '</p>';
$html .= '<p class="aksrt-widget__subtitle">' . nl2br(wp_kses_post((string) $instance['subtitle'])) . '</p>';
}
$html .= '</div><nav class="aksrt-widget__title-links" aria-label="' . esc_attr__('模块链接', 'aksrt') . '">';
foreach (['title_link', 'title_link2', 'title_link3'] as $index => $key) {
@@ -83,6 +90,23 @@ final class FrontendRenderer {
return $html . '</nav></header>';
}
private static function reference_title(array $instance): string {
$title = trim((string) ($instance['title'] ?? ''));
if ($title === '') return '';
$title = wp_kses_post($title);
$highlight = trim((string) ($instance['title_highlight'] ?? ''));
if ($highlight !== '') $title = str_replace(esc_html($highlight), '<span class="title-highlight ' . esc_attr((string) ($instance['title_highlight_color'] ?? 'focus-color')) . '">' . esc_html($highlight) . '</span>', $title);
$subtitle = !empty($instance['subtitle']) ? wp_kses_post((string) $instance['subtitle']) : '';
if (($instance['title_style'] ?? '') === 'big') {
$links = '';
foreach (['title_link', 'title_link2', 'title_link3'] as $index => $key) { $link = RendererHelpers::link((array) ($instance[$key] ?? [])); if ($link['url'] && $link['text']) $links .= '<a class="but ' . esc_attr((string) ($instance['title_link_style']['color_' . ($index + 1)] ?? '')) . '" href="' . $link['url'] . '"' . RendererHelpers::link_attrs($link) . '>' . esc_html($link['text']) . '</a>'; }
return '<div class="big-title-wrap"><h2 class="big-title">' . $title . '</h2>' . ($subtitle ? '<p class="big-title-sub muted-2-color">' . $subtitle . '</p>' : '') . ($links ? '<div class="flex jc big-title-buts">' . $links . '</div>' : '') . '</div>';
}
$link = RendererHelpers::link((array) ($instance['title_link'] ?? []));
$more = $link['url'] ? '<div class="pull-right em09 mt3"><a class="muted-2-color" href="' . $link['url'] . '"' . RendererHelpers::link_attrs($link) . '>' . esc_html($link['text'] ?: __('更多', 'aksrt')) . '</a></div>' : '';
return '<div class="box-body notop"><div class="title-theme">' . $title . ($subtitle ? '<small class="ml10">' . $subtitle . '</small>' : '') . $more . '</div></div>';
}
private static function style(array $instance): string {
$properties = [];
foreach (['layout_pin_pc' => 'space', 'layout_pin_m' => 'space-m'] as $key => $prefix) {
+62 -23
View File
@@ -8,8 +8,6 @@ defined('ABSPATH') || exit;
* Builds post queries exclusively from normalized, saved widget settings.
*/
final class PostQuery {
private const PAY_TYPES = ['reading', 'download', 'gallery', 'video', 'vip'];
public static function build(array $instance, int $page = 1, array $segment = []): \WP_Query {
return new \WP_Query(self::arguments($instance, $page, $segment));
}
@@ -26,28 +24,60 @@ final class PostQuery {
'ignore_sticky_posts' => true,
'no_found_rows' => false,
];
if (array_key_exists('paginate', $config) && in_array((string) $config['paginate'], ['', 'none'], true)) {
$args['no_found_rows'] = true;
$args['paged'] = 1;
}
$categories = RendererHelpers::ids((string) ($config['category_ids'] ?? ''));
if (!empty($config['category_id'])) {
$categories = [(int) $config['category_id']];
}
if ($categories) {
$args['category__in'] = $categories;
}
$tags = RendererHelpers::ids((string) ($config['tag_ids'] ?? ''));
// The modern tab widget uses Zibll's signed `cat` syntax. Legacy
// category_ids remains available only to the unrelated generic posts
// widgets; a tab segment must never fall back to it.
$is_reference_segment = array_key_exists('cat', $segment) || array_key_exists('topics', $segment) || array_key_exists('zibpay_type', $segment);
$cat_raw = array_key_exists('cat', $config) ? (string) $config['cat'] : ($is_reference_segment ? '' : (string) ($config['category_ids'] ?? ''));
$categories = self::signed_ids($cat_raw);
if (!empty($config['category_id'])) $categories = [(int) $config['category_id']];
$cat_in = array_values(array_filter($categories, static fn(int $id): bool => $id > 0));
$cat_not = array_values(array_map('abs', array_filter($categories, static fn(int $id): bool => $id < 0)));
if ($cat_in) $args['category__in'] = $cat_in;
if ($cat_not) $args['category__not_in'] = $cat_not;
$tags = $is_reference_segment ? [] : RendererHelpers::ids((string) ($config['tag_ids'] ?? ''));
if ($tags) {
$args['tag__in'] = $tags;
}
$topics_raw = (string) ($config['topics'] ?? '');
$topics = self::signed_ids($topics_raw);
if ($topics) {
$tax_query = [];
$topic_in = array_values(array_filter($topics, static fn(int $id): bool => $id > 0));
$topic_not = array_values(array_map('abs', array_filter($topics, static fn(int $id): bool => $id < 0)));
if ($topic_in) $tax_query[] = ['taxonomy' => 'topics', 'field' => 'term_id', 'terms' => $topic_in, 'include_children' => true];
if ($topic_not) $tax_query[] = ['taxonomy' => 'topics', 'field' => 'term_id', 'terms' => $topic_not, 'operator' => 'NOT IN', 'include_children' => true];
if (count($tax_query) > 1) $tax_query = array_merge(['relation' => 'AND'], $tax_query);
if ($tax_query) $args['tax_query'] = $tax_query;
}
$days = max(0, (int) ($config['limit_day'] ?? 0));
if ($days > 0) {
$args['date_query'] = [['after' => gmdate('Y-m-d', time() - ($days * DAY_IN_SECONDS)), 'inclusive' => true]];
$now = function_exists('current_time') ? current_time('timestamp') : time();
$format = static function (int $timestamp): string {
return function_exists('wp_date') ? wp_date('Y-m-d H:i:s', $timestamp) : date('Y-m-d H:i:s', $timestamp);
};
$args['date_query'] = [['after' => $format($now - ($days * DAY_IN_SECONDS)), 'before' => $format($now), 'inclusive' => true]];
}
self::apply_order($args, (string) ($config['orderby'] ?? 'date'), (string) ($config['order'] ?? 'DESC'));
self::apply_pay_type($args, (string) ($config['pay_type'] ?? ''));
$pay = array_key_exists('zibpay_type', $config) ? $config['zibpay_type'] : ($is_reference_segment ? '' : ($config['pay_type'] ?? ''));
self::apply_pay_type($args, $pay);
return $args;
}
/** @return list<int> */
private static function signed_ids(string $raw): array {
$tokens = preg_split('/[,,\s]+/', trim($raw), -1, PREG_SPLIT_NO_EMPTY) ?: [];
$out = [];
foreach ($tokens as $token) if (preg_match('/^-?\d+$/', (string) $token)) $out[] = (int) $token;
return array_values(array_unique(array_filter($out, static fn(int $id): bool => $id !== 0)));
}
public static function segment(array $instance, string $segment): array {
$tabs = array_values((array) ($instance['tabs'] ?? []));
if (!$tabs) {
@@ -61,16 +91,19 @@ final class PostQuery {
$args['order'] = strtoupper($order) === 'ASC' ? 'ASC' : 'DESC';
$meta = [
'views' => class_exists('AKSRT\\Core\\DomainModel') ? \AKSRT\Core\DomainModel::POST_VIEW_COUNT_KEY : 'aksrt_post_views',
'likes' => '_aksrt_like_count',
'favorites' => '_aksrt_favorite_count',
'like' => '_aksrt_like_count', 'likes' => '_aksrt_like_count',
'favorite' => '_aksrt_favorite_count', 'favorites' => '_aksrt_favorite_count',
'downloads' => class_exists('AKSRT\\Core\\DomainModel') ? \AKSRT\Core\DomainModel::RESOURCE_DOWNLOAD_COUNT_KEY : '_aksrt_res_download_count',
'zibpay_price' => class_exists('AKSRT\\Core\\DomainModel') ? \AKSRT\Core\DomainModel::PAY_PRICE_KEY : '_aksrt_pay_price',
'zibpay_points_price' => '_aksrt_pay_points_price',
'sales_volume' => class_exists('AKSRT\\Core\\DomainModel') ? \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY : '_aksrt_sales_volume',
];
if (isset($meta[$orderby])) {
$args['meta_key'] = $meta[$orderby];
$args['orderby'] = 'meta_value_num';
return;
}
if ($orderby === 'comments') {
if ($orderby === 'comments' || $orderby === 'comment_count') {
$args['orderby'] = 'comment_count';
} elseif (in_array($orderby, ['date', 'modified', 'rand'], true)) {
$args['orderby'] = $orderby;
@@ -79,16 +112,22 @@ final class PostQuery {
}
}
private static function apply_pay_type(array &$args, string $pay_type): void {
if (!in_array($pay_type, self::PAY_TYPES, true)) {
return;
}
private static function apply_pay_type(array &$args, $pay_type): void {
$values = is_array($pay_type) ? array_map('strval', $pay_type) : array_filter(array_map('trim', preg_split('/[,,\s]+/', (string) $pay_type) ?: []));
$map = ['1' => 'reading', '2' => 'download', '5' => 'gallery', '6' => 'video', 'reading' => 'reading', 'download' => 'download', 'gallery' => 'gallery', 'video' => 'video', 'vip' => 'vip'];
$values = array_values(array_unique(array_filter(array_map(static fn($value): string => $map[(string) $value] ?? '', $values))));
if (!$values) return;
$type_key = class_exists('AKSRT\\Core\\DomainModel') ? \AKSRT\Core\DomainModel::PAY_TYPE_KEY : '_aksrt_pay_type';
$meta_key = class_exists('AKSRT\\Core\\DomainModel') ? \AKSRT\Core\DomainModel::PAY_META_KEY : '_aksrt_pay';
$args['meta_query'] = [[
$pay_query = [
'relation' => 'OR',
['key' => $type_key, 'value' => $pay_type, 'compare' => '='],
['key' => $meta_key, 'value' => '"pay_type";s:' . strlen($pay_type) . ':"' . $pay_type . '"', 'compare' => 'LIKE'],
]];
['key' => $type_key, 'value' => $values, 'compare' => 'IN'],
];
// Some AKSRT installations only have the structured pay array. LIKE
// accepts one scalar per clause, so emit a clause for each allowed type.
foreach ($values as $value) {
$pay_query[] = ['key' => $meta_key, 'value' => '"' . $value . '"', 'compare' => 'LIKE'];
}
$args['meta_query'] = [$pay_query];
}
}
+15 -10
View File
@@ -26,15 +26,17 @@ final class PostRenderer {
public static function items(\WP_Query $query, array $instance, string $slug, int $page = 1): string {
if (!$query->have_posts()) {
return RendererHelpers::empty_state(__('暂无文章', 'aksrt'));
return RendererHelpers::empty_state(__('暂无内容', 'aksrt'));
}
$style = (string) ($instance['style'] ?? 'card');
$mode = in_array($style, ['list', 'card', 'mini'], true) ? $style : 'card';
$config = [
'type' => $mode === 'card' ? 'card' : 'list',
'show_number' => $slug === 'hot_posts',
'show_thumb' => $slug !== 'hot_posts' || !array_key_exists('show_thumbnail', $instance) || !empty($instance['show_thumbnail']),
'show_meta' => true,
'show_number' => $slug === 'hot_posts' || in_array('show_number', (array) ($instance['mini_opt'] ?? []), true),
'show_thumb' => $slug === 'tab_posts'
? in_array('show_thumb', (array) ($instance['mini_opt'] ?? []), true)
: ($slug !== 'hot_posts' || !array_key_exists('show_thumbnail', $instance) || !empty($instance['show_thumbnail'])),
'show_meta' => $slug !== 'tab_posts' || in_array('show_meta', (array) ($instance['mini_opt'] ?? []), true),
'number_offset' => ($page - 1) * max(1, (int) ($instance['count'] ?? 8)),
'wrapper_class' => 'aksrt-widget-posts aksrt-widget-posts--' . $slug,
'widget_style' => $mode,
@@ -77,11 +79,14 @@ final class PostRenderer {
}
private static function pagination(array $instance, int $page, int $max_pages): string {
$mode = (string) ($instance['paginate'] ?? 'none');
if ($max_pages <= 1 || $mode === 'none') {
$mode = (string) ($instance['paginate'] ?? '');
if ($max_pages <= 1) {
return '';
}
if ($mode === 'numbered') {
if ($mode === '' || $mode === 'none') {
return '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
}
if ($mode === 'number' || $mode === 'numbered') {
$start = max(1, min($page - 2, $max_pages - 4));
$end = min($max_pages, max(5, $page + 2));
$html = '<nav class="aksrt-widget-pagination" aria-label="' . esc_attr__('文章分页', 'aksrt') . '">';
@@ -90,9 +95,9 @@ final class PostRenderer {
}
return $html . '</nav>';
}
$label = $mode === 'infinite' ? __('继续加载', 'aksrt') : __('加载更多', 'aksrt');
$class = $mode === 'infinite' ? 'aksrt-widget-load-more ias-btn lazyload' : 'aksrt-widget-load-more';
$action = $mode === 'infinite' ? ' lazyload-action="ias"' : '';
$label = __('加载更多', 'aksrt');
$class = 'aksrt-widget-load-more';
$action = '';
return '<button class="' . $class . '"' . $action . ' type="button" data-next-page="' . (int) ($page + 1) . '" data-mode="' . esc_attr($mode) . '">' . esc_html($label) . '</button>';
}
}
+23 -5
View File
@@ -104,20 +104,38 @@ final class ContentRenderer {
}
private static function tab_posts(array $instance, array $context): string {
$tabs = array_values((array) $instance['tabs']);
$style = in_array(($instance['style'] ?? 'mini'), ['list', 'card', 'mini'], true) ? (string) $instance['style'] : 'mini';
$tabs = array_values((array) ($instance['tabs'] ?? []));
if (!$tabs) {
return RendererHelpers::empty_state(__('请配置文章栏目', 'aksrt'));
}
$buttons = '';
$key = sanitize_html_class((string) ($context['widget_id'] ?? 'preview')) ?: 'preview';
$panel_id = 'aksrt-tab-panel-' . $key;
$ajax_url = rest_url('aksrt/v1/widgets/tab_posts/items');
$first_tab_id = 'aksrt-tab-' . $key . '-0';
$placeholder_type = $style === 'card' ? 'card' : 'lists';
$placeholder = $style === 'mini'
? str_repeat('<div class="posts-mini"><div class="placeholder k1"></div></div>', max(1, (int) ($instance['count'] ?? 6)))
: aksrt_render_placeholder($placeholder_type, max(1, (int) ($instance['count'] ?? 6)));
$panels = '';
$visible_index = 0;
foreach ($tabs as $index => $tab) {
$tab_id = 'aksrt-tab-' . $key . '-' . $index;
$buttons .= '<button type="button" role="tab" id="' . esc_attr($tab_id) . '" aria-controls="' . esc_attr($panel_id) . '" aria-selected="' . ($index === 0 ? 'true' : 'false') . '" tabindex="' . ($index === 0 ? '0' : '-1') . '" data-segment="' . $index . '">' . esc_html((string) ($tab['title'] ?? sprintf(__('栏目 %d', 'aksrt'), $index + 1))) . '</button>';
if (!is_array($tab) || trim((string) ($tab['title'] ?? '')) === '') continue;
$tab_id = 'aksrt-tab-' . $key . '-' . $visible_index;
$selected = $visible_index === 0;
$buttons .= '<li class="' . ($selected ? 'active' : '') . '"><a' . ($selected ? '' : ' data-ajax') . ' data-toggle="tab" href="#' . esc_attr($panel_id . '-' . $visible_index) . '" role="tab" id="' . esc_attr($tab_id) . '" aria-controls="' . esc_attr($panel_id . '-' . $visible_index) . '" aria-selected="' . ($selected ? 'true' : 'false') . '" data-segment="' . (int) $index . '">' . wp_kses_post((string) $tab['title']) . '</a></li>';
if ($selected) {
$payload = PostRenderer::payload($instance, 'tab_posts', 1, (string) $index);
$body = $payload['html'] . $payload['paginate_html'] . '<div class="post_ajax_loader" style="display:none;">' . $placeholder . '</div>';
} else {
$body = '<span class="post_ajax_trigger hide"><a ajaxpager-target=".widget-ajaxpager" class="ajax_load ajax-next ajax-open" data-aksrt-load="1" no-scroll="true" href="' . esc_url(add_query_arg(['widget' => 'tab_posts', 'instance' => (string) ($context['widget_id'] ?? ''), 'page' => 1, 'segment' => $index], $ajax_url)) . '"></a></span><div class="post_ajax_loader" style="display:none;">' . $placeholder . '</div>';
}
$panels .= '<div class="tab-pane fade' . ($selected ? ' active in' : '') . '" id="' . esc_attr($panel_id . '-' . $visible_index) . '" role="tabpanel" aria-labelledby="' . esc_attr($tab_id) . '"><div class="widget-ajaxpager" data-widget-items data-widget="tab_posts" data-instance="' . esc_attr((string) ($context['widget_id'] ?? '')) . '" data-segment="' . (int) $index . '" aria-live="polite" aria-busy="false">' . $body . '</div></div>';
$visible_index++;
}
$payload = PostRenderer::payload($instance, 'tab_posts', 1, '0');
return '<div class="aksrt-widget-tabs" data-widget="tab_posts" data-instance="' . esc_attr((string) ($context['widget_id'] ?? '')) . '"><div class="aksrt-widget-tabs__nav" role="tablist" aria-label="' . esc_attr__('文章栏目', 'aksrt') . '">' . $buttons . '</div><div class="aksrt-widget-tabs__panel" role="tabpanel" id="' . esc_attr($panel_id) . '" aria-labelledby="' . esc_attr($first_tab_id) . '" tabindex="0" data-widget-items data-segment="0" aria-live="polite" aria-busy="false">' . $payload['html'] . $payload['paginate_html'] . '</div></div>';
if ($buttons === '') return RendererHelpers::empty_state(__('请配置文章栏目', 'aksrt'));
return '<div class="widget-tab-post style-' . esc_attr($style) . ($style === 'mini' ? ' posts-mini-lists zib-widget' : ' index-tab') . '" data-widget="tab_posts" data-instance="' . esc_attr((string) ($context['widget_id'] ?? '')) . '"><div class="relative' . ($style === 'card' ? ' mb20' : '') . '"><ul class="list-inline scroll-x no-scrollbar' . ($style === 'mini' ? ' tab-nav-theme' : '') . '" role="tablist">' . $buttons . '</ul></div><div class="tab-content">' . $panels . '</div></div>';
}
private static function post_pay(array $instance): string {
+77 -43
View File
@@ -6,56 +6,90 @@ use AKSRT\WidgetSystem\RendererHelpers;
defined('ABSPATH') || exit;
/** Render full sliders and the slider/media combination. */
/** Reference-compatible renderer for the modern Zibll slider widgets. */
final class SliderRenderer {
public static function render(string $slug, array $instance, array $context = []): string {
unset($context);
$slides = '';
foreach ((array) $instance['slides'] as $slide) {
if (!is_array($slide) || (empty($slide['background']) && empty($slide['background_video']))) {
continue;
}
$link = RendererHelpers::link((array) ($slide['link'] ?? []));
if (!empty($slide['background_video'])) {
$video = esc_url((string) $slide['background_video']);
$video_attr = aksrt_is_lazy('lazy_sider')
? ' class="lazyload" src="" data-src="' . $video . '"'
: ' src="' . $video . '"';
$media = '<video' . $video_attr . ' muted loop playsinline preload="metadata"></video>';
} else {
$media = RendererHelpers::image((string) $slide['background'], (string) ($slide['title'] ?? ''), '', 'lazy_sider', aksrt_get_lazy_thumb('lg'));
}
return $slug === 'slider_media_combo' ? self::combo($instance) : self::slider((array) ($instance['slides'] ?? []), (array) ($instance['option'] ?? []), true);
}
private static function slider(array $slides, array $option, bool $with_margin = false): string {
$option = array_merge(['direction' => 'horizontal', 'loop' => true, 'button' => true, 'pagination' => true, 'effect' => 'slide', 'scale_height' => true, 'scale' => 35, 'auto_height' => false, 'max_height' => 500, 'min_height' => 180, 'pc_height' => 400, 'm_height' => 200, 'spacebetween' => 15, 'speed' => 0, 'autoplay' => true, 'interval' => 4], $option);
$is_mobile = function_exists('wp_is_mobile') && wp_is_mobile();
$items = ''; $count = 0;
foreach ($slides as $slide) {
if (!is_array($slide)) continue;
$hide = (string) ($slide['hide'] ?? '');
if (($is_mobile && $hide === 'm') || (!$is_mobile && $hide === 'pc')) continue;
$background = esc_url((string) ($slide['background'] ?? '')); $video = esc_url((string) ($slide['background_video'] ?? ''));
if ($background === '' && ($video === '' || $is_mobile)) continue;
$alt = (string) (($slide['text']['title'] ?? '') ?: __('幻灯片', 'aksrt'));
$slide_class = '';
if ($video !== '' && !$is_mobile) { $slide_class = ' slide-background-video'; $media = '<video autoplay loop muted playsinline class="fit-cover radius8" src="' . $video . '"' . ($background !== '' ? ' poster="' . $background . '"' : '') . '></video><div class="absolute"></div>'; }
else $media = '<img class="radius8 swiper-lazy" src="' . esc_url(defined('AKSRT_URI') ? AKSRT_URI . '/assets/img/thumbnail-lg.svg' : $background) . '" data-src="' . $background . '" alt="' . esc_attr($alt) . '">';
$layers = '';
foreach ((array) ($slide['layers'] ?? []) as $layer) {
if (!empty($layer['image'])) {
$layers .= '<img' . aksrt_get_lazy_attr('lazy_sider', (string) $layer['image'], 'aksrt-slide-layer', aksrt_get_lazy_thumb('lg')) . ' alt="" data-swiper-parallax="' . (int) ($layer['parallax'] ?? 0) . '" data-swiper-parallax-scale="' . esc_attr((string) ((float) ($layer['scale'] ?? 100) / 100)) . '">';
}
foreach ((array) ($slide['image_layer'] ?? []) as $layer) {
if (!is_array($layer) || empty($layer['image'])) continue;
$attrs = ''; $parallax = (int) ($layer['parallax'] ?? 0); $scale = (float) ($layer['parallax_scale'] ?? 100) / 100; $opacity = (float) ($layer['parallax_opacity'] ?? 100) / 100;
if ($parallax) $attrs .= ' data-swiper-parallax="' . $parallax . '%"'; if ($scale !== 1.0) $attrs .= ' data-swiper-parallax-scale="' . esc_attr((string) $scale) . '"'; if ($opacity !== 1.0) $attrs .= ' data-swiper-parallax-opacity="' . esc_attr((string) $opacity) . '"';
$class = !empty($layer['free_size']) ? ' slide-layer text-' . esc_attr((string) ($layer['align'] ?? 'center')) : '';
$layers .= '<div class="absolute' . $class . '"' . $attrs . '><img class="radius8 swiper-lazy" src="' . esc_url(defined('AKSRT_URI') ? AKSRT_URI . '/assets/img/thumbnail-lg.svg' : (string) $layer['image']) . '" data-src="' . esc_url((string) $layer['image']) . '" alt="' . esc_attr($alt) . '"></div>';
}
$copy = '<div class="aksrt-slide-copy is-' . esc_attr((string) ($slide['text_align'] ?? 'left-bottom')) . '"><h3>' . esc_html((string) ($slide['title'] ?? '')) . '</h3><p>' . esc_html((string) ($slide['description'] ?? '')) . '</p></div>';
$body = $media . $layers . $copy;
if ($link['url']) {
$body = '<a href="' . $link['url'] . '"' . RendererHelpers::link_attrs($link) . '>' . $body . '</a>';
}
$slides .= '<div class="swiper-slide' . (!empty($slide['hide_mobile']) ? ' aksrt-slide--hide-mobile' : '') . '">' . $body . '</div>';
$text = (array) ($slide['text'] ?? []); $copy = '';
if (!empty($text['title'])) { $style = ''; if (!empty($text['text_size_pc'])) $style .= '--text-size-pc:' . (int) $text['text_size_pc'] . 'px;'; if (!empty($text['text_size_m'])) $style .= '--text-size-m:' . (int) $text['text_size_m'] . 'px;'; $copy = '<div class="abs-center slide-text ' . esc_attr((string) ($text['text_align'] ?? 'left-bottom')) . '"' . ($style ? ' style="' . esc_attr($style) . '"' : '') . '><div class="slide-title">' . wp_kses_post((string) $text['title']) . '</div>' . (!empty($text['desc']) ? '<div class="slide-desc">' . wp_kses_post((string) $text['desc']) . '</div>' : '') . '</div>'; if (!empty($text['parallax'])) $copy = '<div class="absolute" data-swiper-parallax="' . (int) $text['parallax'] . '">' . $copy . '</div>'; }
$body = $media . $layers . $copy; $link = RendererHelpers::link((array) ($slide['link'] ?? [])); if ($link['url'] !== '') $body = '<a href="' . $link['url'] . '"' . RendererHelpers::link_attrs($link) . '>' . $body . '</a>';
$items .= '<div class="swiper-slide' . esc_attr($slide_class) . '">' . $body . '</div>'; $count++;
}
if ($slides === '') {
return RendererHelpers::empty_state(__('请配置幻灯片', 'aksrt'));
if (!$count) return RendererHelpers::empty_state(__('请配置幻灯片', 'aksrt'));
$class = 'new-swiper slide-widget' . ($with_margin ? ' mb20' : '') . ($option['scale_height'] && $option['direction'] !== 'vertical' ? ' scale-height' : '') . ($count === 1 ? ' swiper-container-initialized' : ''); $attrs = ' data-direction="' . esc_attr((string) $option['direction']) . '" data-effect="' . esc_attr((string) $option['effect']) . '"';
if (!empty($option['loop']) && $count > 1) $attrs .= ' data-loop="true"'; if (!empty($option['autoplay']) && $count > 1) $attrs .= ' data-autoplay="1" data-interval="' . ((int) $option['interval'] * 1000) . '"'; if ((int) $option['speed'] > 0) $attrs .= ' data-speed="' . (int) $option['speed'] . '"'; $attrs .= ' data-spaceBetween="' . (int) $option['spacebetween'] . '"';
if ($option['auto_height']) $attrs .= ' auto-height="true" style="--max-height:' . (int) $option['max_height'] . 'px;--min-height:' . (int) $option['min_height'] . 'px;"'; else $attrs .= ' style="' . esc_attr($option['scale_height'] && $option['direction'] !== 'vertical' ? '--scale-height:' . (int) $option['scale'] . '%;' : '--pc-height:' . (int) $option['pc_height'] . 'px;--m-height:' . (int) $option['m_height'] . 'px;') . '"';
$html = '<div class="' . esc_attr($class) . '"' . $attrs . '><div class="swiper-wrapper">' . $items . '</div>'; if (!empty($option['button']) && $count > 1) $html .= '<div class="swiper-button-prev"></div><div class="swiper-button-next"></div>'; if (!empty($option['pagination']) && $count > 1) $html .= '<div class="swiper-pagination kaoyou"></div>'; return '<div class="relative zib-slider">' . $html . '</div>';
}
private static function combo(array $instance): string {
$option = (array) ($instance['option'] ?? []); $option['direction'] = 'horizontal'; $option['scale_height'] = true; $option['scale'] = (int) ($instance['slide_scale'] ?? 50);
$slide_list = (array) ($instance['slides'] ?? []);
$slider = !empty($slide_list[0]) ? self::slider($slide_list, $option) : '';
$covers = '';
$is_mobile = function_exists('wp_is_mobile') && wp_is_mobile();
$cover_list = array_values((array) ($instance['covers'] ?? []));
$is_row = count($cover_list) > 1;
$pc_row = max(1, min(3, (int) ($instance['pc_row'] ?? 2)));
$m_row = max(1, min(3, (int) ($instance['m_row'] ?? 2)));
$row_class = 'col-sm-' . (int) (12 / $pc_row) . ($m_row > 1 ? ' col-xs-' . (int) (12 / $m_row) : '');
foreach ($cover_list as $cover) {
if (!is_array($cover)) continue;
$hide = (string) ($cover['hide'] ?? '');
if (($is_mobile && $hide === 'm') || (!$is_mobile && $hide === 'pc')) continue;
$image = esc_url((string) ($cover['image'] ?? ''));
$video = esc_url((string) ($cover['video'] ?? ''));
if ($image === '' && ($video === '' || $is_mobile)) continue;
$media = $video !== '' && !$is_mobile ? '<video autoplay loop muted playsinline class="fit-cover" src="' . $video . '"' . ($image !== '' ? ' poster="' . $image . '"' : '') . '></video>' : '<img class="fit-cover" src="' . $image . '" alt="' . esc_attr(wp_strip_all_tags((string) ($cover['title'] ?? ''))) . '">';
$mask = max(0, min(90, (int) ($instance['mask_opacity'] ?? 0))) / 100;
$card = $media . ($mask ? '<i class="graphic-mask" style="opacity:' . esc_attr((string) $mask) . '"></i>' : '') . (!empty($cover['title']) ? '<div class="abs-center text-center graphic-text this-font">' . wp_kses_post((string) $cover['title']) . '</div>' : '');
$link = RendererHelpers::link((array) ($cover['link'] ?? []));
$card = $link['url'] !== '' ? '<a href="' . $link['url'] . '"' . RendererHelpers::link_attrs($link) . '>' . $card . '</a>' : '<span>' . $card . '</span>';
$covers .= '<div class="' . ($is_row ? esc_attr($row_class) : '') . '"><div class="graphic-cover-item noshadow mb10">' . $card . '</div></div>';
}
$slider = '<div class="new-swiper aksrt-widget-swiper aksrt-widget-slider" data-aksrt-swiper="slider" data-direction="' . esc_attr((string) $instance['direction']) . '" data-effect="' . esc_attr((string) $instance['effect']) . '"' . (!empty($instance['loop']) ? ' data-loop="true"' : '') . (!empty($instance['autoplay']) ? ' data-autoplay="1"' : '') . ' data-interval="' . ((int) $instance['interval'] * 1000) . '" data-speed="' . (int) $instance['speed'] . '" data-spaceBetween="' . (int) $instance['spacebetween'] . '" style="--aksrt-slider-height:' . (int) $instance['pc_height'] . 'px;--aksrt-slider-height-m:' . (int) $instance['m_height'] . 'px"><div class="swiper-wrapper">' . $slides . '</div>';
if (!empty($instance['button'])) {
$slider .= '<button type="button" class="swiper-button-prev" aria-label="' . esc_attr__('上一张', 'aksrt') . '"></button><button type="button" class="swiper-button-next" aria-label="' . esc_attr__('下一张', 'aksrt') . '"></button>';
if ($covers !== '') {
$cover_style = '--font-size:' . (int) ($instance['font_size_pc'] ?? 16) . 'px;--font-size-sm:' . (int) ($instance['font_size_m'] ?? 14) . 'px;';
if (!empty($instance['font_bold'])) $cover_style .= '--font-weight:bold;--font-weight-sm:bold;';
if (!empty($instance['font_color'])) $cover_style .= '--color:' . $instance['font_color'] . ';--color-sm:' . $instance['font_color'] . ';';
$covers = '<div class="widget-graphic-cover" style="' . esc_attr($cover_style) . '">' . ($is_row ? '<div class="row gutters-5">' : '') . $covers . ($is_row ? '</div>' : '') . '</div>';
}
if (!empty($instance['pagination'])) {
$slider .= '<div class="swiper-pagination"></div>';
}
$slider .= '</div>';
if ($slug !== 'slider_media_combo') {
return $slider;
}
$cards = '';
foreach ((array) ($instance['side_cards'] ?? []) as $card) {
$cards .= RendererHelpers::card_media((array) $card);
}
return '<div class="aksrt-slider-combo"><div class="aksrt-slider-combo__main">' . $slider . '</div><aside class="aksrt-slider-combo__side">' . $cards . '</aside></div>';
if ($slider === '' && $covers === '') return RendererHelpers::empty_state(__('请配置幻灯片或卡片', 'aksrt'));
$layout = max(0, min(100, (int) ($instance['layout_ratio'] ?? 60)));
$slide_scale = max(0, (int) ($instance['slide_scale'] ?? 50));
$rows = max(1, (int) ($instance['pc_row'] ?? 2));
$columns = max(1, (int) ($instance['pc_col'] ?? 2));
$base = $layout < 100 ? (($layout * $slide_scale) / (100 - $layout)) : 0;
$cover_ratio = (($base - (($columns - 1) * 3)) / $columns) * $rows;
$style = '--layout-ratio:' . $layout . '%;--cover-ratio:' . $cover_ratio . '%;--m-cover-ratio:' . (int) ($instance['m_cover_ratio'] ?? 45) . '%;--cover-cols:' . $rows . ';';
$class = 'flex widget-slider-graphic m-' . sanitize_html_class((string) ($instance['m_layout_type'] ?? 'vertical'));
$html = '<div class="' . esc_attr($class) . '" style="' . esc_attr($style) . '" data-row="' . $rows . '" data-col="' . $columns . '">';
if ($slider !== '') $html .= '<div class="slider-col mb20">' . $slider . '</div>';
if ($covers !== '') $html .= '<div class="cover-col mb10">' . $covers . '</div>';
return $html . '</div>';
}
}
+3 -3
View File
@@ -35,7 +35,7 @@ final class WidgetCatalog {
['hot_posts', '热榜文章', '按浏览、评论、收藏等维度排行', 'sidebar'],
['post_pay', '付费购买', '显示当前文章的知识付费购买模块', 'sidebar'],
['posts', '文章列表', '多条件筛选和多样式文章列表', 'wide'],
['tab_posts', '多栏目文章', '通过多个栏目切换文章列表', 'wide'],
['tab_posts', '多栏目文章(新)', '通过多个栏目切换文章列表', 'wide'],
['oneline_posts', '横向滚动文章', '单行横向滚动文章列表', 'wide'],
],
'knowledge_product' => [
@@ -48,7 +48,7 @@ final class WidgetCatalog {
['tilt_cases', '知识产品 3D 展示', '带 3D 交互的知识产品案例展示', 'wide'],
],
'slider' => [
['slider', '幻灯片', '支持图片、视频、文字和视差图层', 'wide'],
['slider', '幻灯片(新)', '支持图片、视频、文字和视差图层', 'wide'],
['slider_media_combo', '幻灯片加图文视频卡片', '幻灯片与图文视频卡片组合布局', 'wide'],
],
'user' => [
@@ -70,7 +70,7 @@ final class WidgetCatalog {
'renderer' => $slug,
'size' => $size,
'common' => [
'title' => !in_array($slug, ['post_pay', 'layout_gap'], true),
'title' => !in_array($slug, ['post_pay', 'layout_gap', 'slider', 'slider_media_combo'], true),
'affix' => $size === 'sidebar',
],
'fields' => WidgetSchemas::for($slug),
+106 -30
View File
@@ -145,19 +145,45 @@ final class WidgetSchemas {
$fields[] = ['id' => 'autoplay', 'type' => 'switcher', 'label' => '自动滚动', 'default' => true];
}
if ($slug === 'tab_posts') {
$fields[] = [
'id' => 'tabs', 'type' => 'group', 'label' => '栏目', 'max_items' => 10,
'fields' => [
['id' => 'title', 'type' => 'text', 'label' => '栏目标题', 'default' => ''],
['id' => 'category_id', 'type' => 'number', 'label' => '分类 ID', 'default' => 0, 'min' => 0],
['id' => 'tag_ids', 'type' => 'text', 'label' => '标签 ID', 'default' => '', 'sanitize' => 'ids'],
['id' => 'pay_type', 'type' => 'select', 'label' => '付费类型', 'default' => '', 'options' => self::pay_types()],
],
];
return self::tab_posts_fields();
}
return $fields;
}
private static function tab_posts_fields(): array {
return [
['id' => 'style', 'type' => 'radio', 'label' => '列表样式', 'default' => 'mini', 'options' => ['list' => '列表样式', 'card' => '卡片样式', 'mini' => 'mini列表']],
['id' => 'mini_opt', 'type' => 'checkbox', 'label' => 'mini列表配置', 'default' => [], 'dependency' => ['style', '==', 'mini'], 'options' => ['show_thumb' => '显示缩略图', 'show_number' => '显示编号(开启翻页后,只在第一页有效)', 'show_meta' => '显示作者、时间、点赞等信息']],
['id' => 'count', 'type' => 'spinner', 'label' => '显示数量', 'default' => 6, 'min' => 4, 'max' => 20, 'step' => 1],
['id' => 'paginate', 'type' => 'radio', 'label' => '翻页按钮', 'default' => '', 'options' => ['' => '不翻页', 'ajax' => 'AJAX追加列表翻页', 'number' => '数字翻页按钮']],
[
'id' => 'tabs', 'type' => 'group', 'label' => '栏目', 'max_items' => 10,
'default' => [
['title' => '热门推荐', 'orderby' => 'views'],
['title' => '最近更新', 'orderby' => 'modified'],
['title' => '猜你喜欢', 'orderby' => 'rand'],
],
'fields' => [
['id' => 'title', 'type' => 'textarea', 'label' => '标题(必填)', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 1]],
['id' => 'cat', 'type' => 'text', 'label' => '分类限制', 'default' => '', 'sanitize' => 'reference_ids'],
['id' => 'topics', 'type' => 'text', 'label' => '专题限制', 'default' => '', 'sanitize' => 'reference_ids', 'description' => '多个 ID 用英文逗号隔开,支持负数排除。'],
['id' => 'zibpay_type', 'type' => 'checkbox', 'label' => '商品类型筛选', 'default' => [], 'options' => ['1' => '付费阅读', '2' => '付费下载', '5' => '付费图片', '6' => '付费视频']],
['id' => 'limit_day', 'type' => 'spinner', 'label' => '发布时间限制', 'default' => 0, 'min' => 0, 'step' => 5, 'max' => 3650],
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => '', 'options' => self::posts_orderby()],
['id' => 'order', 'type' => 'radio', 'label' => '顺序', 'default' => 'desc', 'options' => ['desc' => '降序', 'asc' => '升序']],
],
],
];
}
private static function posts_orderby(): array {
return [
'' => '默认排序', 'modified' => '更新时间', 'date' => '发布时间', 'views' => '浏览数量',
'like' => '点赞数量', 'comment_count' => '评论数量', 'favorite' => '收藏数量',
'zibpay_price' => '销售价格', 'zibpay_points_price' => '积分售价', 'sales_volume' => '销售数量', 'rand' => '随机排序',
];
}
private static function post_query_fields(): array {
return [
['id' => 'category_ids', 'type' => 'text', 'label' => '分类 ID', 'default' => '', 'sanitize' => 'ids'],
@@ -206,31 +232,81 @@ final class WidgetSchemas {
}
private static function slider(string $slug): array {
$asset_uri = function_exists('get_template_directory_uri') ? get_template_directory_uri() . '/assets/img/' : '';
$align_uri = function_exists('get_template_directory_uri') ? get_template_directory_uri() . '/assets/images/slider-align/' : '';
$slide_fields = [
['id' => 'background', 'type' => 'upload', 'label' => '图片背景', 'default' => ''],
['id' => 'background_video', 'type' => 'upload', 'label' => '视频背景', 'default' => '', 'description' => '图片背景、视频背景至少二选一;PC 端视频优先,移动端只显示图片。'],
['id' => 'hide', 'type' => 'radio', 'label' => '显示规则', 'default' => '', 'options' => ['' => '全部显示', 'pc' => 'PC端不显示', 'm' => '移动端不显示']],
['id' => 'link', 'type' => 'link', 'label' => '跳转链接', 'default' => []],
['id' => 'image_layer', 'type' => 'group', 'label' => '叠加图层', 'max_items' => 12, 'fields' => [
['id' => 'image', 'type' => 'upload', 'label' => '图层图片', 'default' => ''],
['id' => 'free_size', 'type' => 'switcher', 'label' => '自由尺寸', 'default' => false],
['id' => 'align', 'type' => 'radio', 'label' => '图层对齐', 'default' => 'center', 'options' => ['left' => '靠左显示', 'center' => '居中显示', 'right' => '靠右显示'], 'dependency' => ['image|free_size', '!=|!=', '|']],
['id' => 'parallax', 'type' => 'spinner', 'label' => '视差滚动', 'default' => 0, 'min' => -200, 'max' => 200, 'step' => 5, 'dependency' => ['image', '!=', '']],
['id' => 'parallax_scale', 'type' => 'spinner', 'label' => '视差缩放', 'default' => 100, 'min' => 1, 'max' => 200, 'step' => 5, 'dependency' => ['image', '!=', '']],
['id' => 'parallax_opacity', 'type' => 'spinner', 'label' => '视差透明', 'default' => 100, 'min' => 1, 'max' => 100, 'step' => 5, 'dependency' => ['image|parallax', '!=|!=', '|']],
]],
['id' => 'text', 'type' => 'accordion', 'label' => '叠加文案', 'fields' => [
['id' => 'title', 'type' => 'textarea', 'label' => '幻灯片文案', 'default' => '', 'allow_html' => true],
['id' => 'desc', 'type' => 'textarea', 'label' => '幻灯片简介', 'default' => '', 'allow_html' => true, 'dependency' => ['title', '!=', '']],
['id' => 'text_align', 'type' => 'image_select', 'label' => '显示位置', 'default' => 'left-bottom', 'options' => ['left-bottom' => $align_uri . 'left-bottom.jpg', 'left-conter' => $align_uri . 'left-conter.jpg', 'conter-conter' => $align_uri . 'conter-conter.jpg', 'conter-bottom' => $align_uri . 'conter-bottom.jpg', 'right-conter' => $align_uri . 'right-conter.jpg', 'right-bottom' => $align_uri . 'right-bottom.jpg']],
['id' => 'text_size_pc', 'type' => 'spinner', 'label' => 'PC端字体大小', 'default' => 30, 'min' => 12, 'max' => 50],
['id' => 'text_size_m', 'type' => 'spinner', 'label' => '移动端字体大小', 'default' => 20, 'min' => 12, 'max' => 50],
['id' => 'parallax', 'type' => 'spinner', 'label' => '文案视差滚动', 'default' => 40, 'min' => -200, 'max' => 200, 'step' => 10],
]],
];
$fields = [
['id' => 'direction', 'type' => 'radio', 'label' => '方向', 'default' => 'horizontal', 'options' => ['horizontal' => '横向', 'vertical' => '纵向']],
['id' => 'effect', 'type' => 'select', 'label' => '切换效果', 'default' => 'slide', 'options' => ['slide' => '滑动', 'fade' => '淡入', 'cube' => '立方体', 'coverflow' => '封面流']],
['id' => 'pc_height', 'type' => 'spinner', 'label' => 'PC 高度', 'default' => 400, 'min' => 120, 'max' => 1000],
['id' => 'm_height', 'type' => 'spinner', 'label' => '移动端高度', 'default' => 240, 'min' => 100, 'max' => 800],
['id' => 'spacebetween', 'type' => 'spinner', 'label' => '间距', 'default' => 15, 'min' => 0, 'max' => 100],
['id' => 'speed', 'type' => 'spinner', 'label' => '切换速度(毫秒)', 'default' => 500, 'min' => 100, 'max' => 5000],
['id' => 'interval', 'type' => 'spinner', 'label' => '自动播放间隔(秒)', 'default' => 4, 'min' => 1, 'max' => 60],
['id' => 'autoplay', 'type' => 'switcher', 'label' => '自动播放', 'default' => true],
['id' => 'loop', 'type' => 'switcher', 'label' => '循环', 'default' => true],
['id' => 'button', 'type' => 'switcher', 'label' => '显示切换按钮', 'default' => true],
['id' => 'pagination', 'type' => 'switcher', 'label' => '显示分页器', 'default' => true],
['id' => 'slides', 'type' => 'group', 'label' => '幻灯片', 'max_items' => 30, 'fields' => [
['id' => 'background', 'type' => 'upload', 'label' => '背景图片', 'default' => ''],
['id' => 'background_video', 'type' => 'upload', 'label' => '背景视频', 'default' => ''],
['id' => 'hide_mobile', 'type' => 'switcher', 'label' => '移动端隐藏', 'default' => false],
['id' => 'link', 'type' => 'link', 'label' => '链接', 'default' => []],
['id' => 'title', 'type' => 'text', 'label' => '标题', 'default' => ''],
['id' => 'description', 'type' => 'textarea', 'label' => '说明', 'default' => ''],
['id' => 'text_align', 'type' => 'select', 'label' => '文字位置', 'default' => 'left-bottom', 'options' => ['left-top' => '左上', 'left-center' => '左中', 'left-bottom' => '左下', 'center' => '居中', 'right-bottom' => '右下']],
['id' => 'layers', 'type' => 'group', 'label' => '图片图层', 'max_items' => 12, 'fields' => [['id' => 'image', 'type' => 'upload', 'label' => '图片', 'default' => ''], ['id' => 'parallax', 'type' => 'spinner', 'label' => '视差', 'default' => 0, 'min' => -300, 'max' => 300], ['id' => 'scale', 'type' => 'slider', 'label' => '缩放', 'default' => 100, 'min' => 10, 'max' => 300]]],
['id' => 'slides', 'type' => 'group', 'label' => '幻灯片内容', 'max_items' => 30, 'min_items' => 1, 'default' => [['background' => $asset_uri . 'slider-bg.jpg', 'image_layer' => [['image' => $asset_uri . 'slider-layer-1.png', 'align' => 'center', 'free_size' => true, 'parallax' => -100, 'parallax_scale' => 100, 'parallax_opacity' => 100], ['image' => $asset_uri . 'slider-layer-2.png', 'align' => 'center', 'free_size' => true, 'parallax' => -50, 'parallax_scale' => 50, 'parallax_opacity' => 30]]]], 'fields' => $slide_fields],
['id' => 'option', 'type' => 'accordion', 'label' => '幻灯片设置', 'default' => ['direction' => 'horizontal', 'button' => true, 'pagination' => true, 'effect' => 'slide', 'auto_height' => false, 'pc_height' => 400, 'm_height' => 240, 'spacebetween' => 15, 'speed' => 0, 'interval' => 4], 'fields' => [
['id' => 'direction', 'type' => 'radio', 'label' => '幻灯片方向', 'default' => 'horizontal', 'options' => ['horizontal' => '左右切换', 'vertical' => '上下切换']],
['id' => 'loop', 'type' => 'switcher', 'label' => '循环切换', 'default' => true],
['id' => 'button', 'type' => 'switcher', 'label' => '显示翻页按钮', 'default' => false],
['id' => 'pagination', 'type' => 'switcher', 'label' => '显示指示器', 'default' => false],
['id' => 'effect', 'type' => 'select', 'label' => '切换动画', 'default' => 'slide', 'options' => ['slide' => '滑动', 'fade' => '淡出淡入', 'cube' => '3D方块', 'coverflow' => '3D滑入', 'flip' => '3D翻转']],
['id' => 'scale_height', 'type' => 'switcher', 'label' => '按比例自动高度', 'default' => true, 'dependency' => ['direction', '!=', 'vertical']],
['id' => 'scale', 'type' => 'spinner', 'label' => '长宽比例', 'default' => 35, 'min' => 10, 'max' => 300, 'step' => 5, 'dependency' => ['direction|scale_height', '!=|!=', 'vertical|']],
['id' => 'auto_height', 'type' => 'switcher', 'label' => '自动高度', 'default' => false, 'dependency' => ['direction|scale_height', '!=|==', 'vertical|']],
['id' => 'max_height', 'type' => 'spinner', 'label' => '最大高度', 'default' => 500, 'min' => 120, 'max' => 800, 'step' => 20, 'dependency' => ['auto_height', '!=', '']],
['id' => 'min_height', 'type' => 'spinner', 'label' => '最小高度', 'default' => 180, 'min' => 100, 'max' => 500, 'step' => 20, 'dependency' => ['auto_height', '!=', '']],
['id' => 'pc_height', 'type' => 'spinner', 'label' => '电脑端高度', 'default' => 400, 'min' => 120, 'max' => 800, 'step' => 20],
['id' => 'm_height', 'type' => 'spinner', 'label' => '移动端高度', 'default' => 200, 'min' => 100, 'max' => 500, 'step' => 20],
['id' => 'spacebetween', 'type' => 'spinner', 'label' => '幻灯片间距', 'default' => 15, 'min' => 0, 'max' => 500, 'step' => 5],
['id' => 'speed', 'type' => 'spinner', 'label' => '切换速度', 'default' => 0, 'min' => 0, 'max' => 3000, 'step' => 100],
['id' => 'autoplay', 'type' => 'switcher', 'label' => '自动播放', 'default' => true],
['id' => 'interval', 'type' => 'spinner', 'label' => '停顿时间', 'default' => 4, 'min' => 0, 'max' => 20, 'dependency' => ['autoplay', '!=', '']],
]],
];
if ($slug === 'slider_media_combo') {
$fields[] = ['id' => 'side_cards', 'type' => 'group', 'label' => '右侧图文视频卡片', 'max_items' => 6, 'fields' => self::media_card_fields()];
$combo_slides = [
['background' => $asset_uri . 'slider-bg.jpg', 'text' => ['title' => '更优雅的 WordPress 主题:AKSRT', 'desc' => '', 'text_align' => 'left-bottom', 'text_size_m' => 16, 'text_size_pc' => 24, 'parallax' => 0]],
['background' => $asset_uri . 'slider-bg.jpg', 'text' => ['title' => '更优雅的 WordPress 主题 AKSRT', 'desc' => '', 'text_align' => 'left-bottom', 'text_size_m' => 16, 'text_size_pc' => 24, 'parallax' => 0]],
];
$combo_option = [
['id' => 'loop', 'type' => 'switcher', 'label' => '循环切换', 'default' => true],
['id' => 'button', 'type' => 'switcher', 'label' => '显示翻页按钮', 'default' => true],
['id' => 'pagination', 'type' => 'switcher', 'label' => '显示指示器', 'default' => true],
['id' => 'effect', 'type' => 'select', 'label' => '切换动画', 'default' => 'slide', 'options' => ['slide' => '滑动', 'fade' => '淡出淡入', 'cube' => '3D方块', 'coverflow' => '3D滑入', 'flip' => '3D翻转']],
['id' => 'spacebetween', 'type' => 'spinner', 'label' => '幻灯片间距', 'default' => 20, 'min' => 0, 'max' => 500, 'step' => 5],
['id' => 'speed', 'type' => 'spinner', 'label' => '切换速度', 'default' => 0, 'min' => 0, 'max' => 3000, 'step' => 100],
['id' => 'autoplay', 'type' => 'switcher', 'label' => '自动播放', 'default' => true],
['id' => 'interval', 'type' => 'spinner', 'label' => '停顿时间', 'default' => 4, 'min' => 0, 'max' => 20, 'dependency' => ['autoplay', '!=', '']],
];
$fields = [
['id' => 'layout_ratio', 'type' => 'slider', 'label' => '幻灯片宽度占比', 'default' => 60, 'min' => 0, 'max' => 100],
['id' => 'slide_scale', 'type' => 'spinner', 'label' => '幻灯片长宽比', 'default' => 50, 'min' => 0, 'max' => 500, 'step' => 5],
['id' => 'pc_row', 'type' => 'button_set', 'label' => '右侧卡片列数', 'default' => 2, 'options' => [1 => '1列', 2 => '2列', 3 => '3列', 4 => '4列']],
['id' => 'pc_col', 'type' => 'button_set', 'label' => '右侧卡片行数', 'default' => 2, 'options' => [1 => '1行', 2 => '2行', 3 => '3行', 4 => '4行']],
['id' => 'm_layout_type', 'type' => 'radio', 'label' => '移动端布局方式', 'default' => 'vertical', 'options' => ['default' => '保持PC端布局', 'vertical' => '上下布局', 'hide_slide' => '隐藏幻灯片', 'hide_cover' => '隐藏卡片']],
['id' => 'm_row', 'type' => 'button_set', 'label' => '移动端卡片单行数量', 'default' => 2, 'dependency' => ['m_layout_type', 'any', 'hide_slide,vertical'], 'options' => [1 => '1个', 2 => '2个', 3 => '3个', 4 => '4个', 6 => '6个']],
['id' => 'm_cover_ratio', 'type' => 'spinner', 'label' => '移动端卡片长宽比', 'default' => 45, 'min' => 5, 'max' => 300, 'step' => 5, 'dependency' => ['m_layout_type', 'any', 'hide_slide,vertical']],
['id' => 'slides', 'type' => 'group', 'label' => '幻灯片内容', 'max_items' => 30, 'min_items' => 1, 'default' => $combo_slides, 'fields' => $slide_fields],
['id' => 'option', 'type' => 'accordion', 'label' => '幻灯片设置', 'default' => ['button' => true, 'pagination' => true, 'effect' => 'slide', 'spacebetween' => 20, 'speed' => 0, 'interval' => 4, 'autoplay' => true, 'loop' => true], 'fields' => $combo_option],
['id' => 'covers', 'type' => 'group', 'label' => '右侧图文视频卡片', 'max_items' => 30, 'default' => array_fill(0, 4, ['image' => $asset_uri . 'user_t.jpg', 'title' => '']), 'fields' => [
['id' => 'image', 'type' => 'upload', 'label' => '图片背景', 'default' => '', 'library' => 'image'], ['id' => 'video', 'type' => 'upload', 'label' => '视频背景', 'default' => '', 'library' => 'video'], ['id' => 'hide', 'type' => 'radio', 'label' => '显示规则', 'default' => '', 'options' => ['' => '全部显示', 'pc' => 'PC端不显示', 'm' => '移动端不显示']], ['id' => 'title', 'type' => 'textarea', 'label' => '文字', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 1]], ['id' => 'link', 'type' => 'link', 'label' => '跳转链接', 'default' => []],
]],
['id' => 'mask_opacity', 'type' => 'slider', 'label' => '遮罩透明度', 'default' => 0, 'min' => 0, 'max' => 90], ['id' => 'font_size_pc', 'type' => 'spinner', 'label' => 'PC端字体大小', 'default' => 16, 'min' => 10, 'max' => 80, 'step' => 2], ['id' => 'font_size_m', 'type' => 'spinner', 'label' => '移动端字体大小', 'default' => 14, 'min' => 10, 'max' => 80, 'step' => 2], ['id' => 'font_bold', 'type' => 'switcher', 'label' => '粗体显示', 'default' => false], ['id' => 'font_color', 'type' => 'color', 'label' => '文字颜色', 'default' => ''],
];
}
return $fields;
}