Files
wordpress-theme-aksrt/src/AKSRT/Widgets/WidgetForumTabTopics.php
T
kate522 11920a2dc6 feat(widgets): forum tabs shared hot payload + terms/boards ajax
Route multi-tab forum topics through HotTopics mini/rank payload and
WidgetAjax style/plate params; wire terms/boards to real REST ajaxpager.
2026-07-21 11:17:01 +08:00

228 lines
8.5 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
/**
* Multi-tab forum topic lists (zibll zib_bbs_widget_ui_tab_posts + shared mini rows).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetForumTabTopics extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_forum_tab_topics',
esc_html__('论坛 - 多栏目帖子', 'aksrt'),
[
'description' => esc_html__('热门/最新/精华等栏目切换的帖子列表(共享迷你行 + 懒加载)。', 'aksrt'),
'classname' => 'widget_aksrt_forum_tab_topics',
]
);
}
public function widget($args, $instance): void {
if (!post_type_exists('aksrt_forum_topic')) {
return;
}
$tabs = $this->normalize_tabs($instance['tabs'] ?? []);
if (!$tabs) {
$tabs = [
['title' => __('最新', 'aksrt'), 'orderby' => 'date'],
['title' => __('热门', 'aksrt'), 'orderby' => 'views'],
['title' => __('精华', 'aksrt'), 'orderby' => 'essence'],
];
}
$count = max(1, min(20, (int) ($instance['count'] ?? 6)));
$style = sanitize_key((string) ($instance['style'] ?? 'mini'));
if (!in_array($style, ['mini', 'rank'], true)) {
$style = 'mini';
}
$plate = max(0, (int) ($instance['plate'] ?? 0));
$uid = 'aksrt-ftab-' . esc_attr($this->id);
// Prefer WidgetAjax forum-topics (HotTopics payload with style/plate).
$rest = rest_url('aksrt/v1/widgets/forum-topics');
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
}
echo '<div class="aksrt-ftab widget-tab-posts" id="' . $uid . '" data-aksrt-tabs="1" data-rest="' . esc_url($rest) . '" data-count="' . esc_attr((string) $count) . '" data-style="' . esc_attr($style) . '" data-plate="' . esc_attr((string) $plate) . '">';
echo '<ul class="aksrt-ftab__nav widget-tab-posts__nav" role="tablist">';
foreach ($tabs as $i => $tab) {
$cls = $i === 0 ? ' is-active' : '';
echo '<li role="presentation"><button type="button" role="tab" class="aksrt-ftab__btn widget-tab-posts__tab' . esc_attr($cls) . '" data-index="' . (int) $i . '" data-orderby="' . esc_attr($tab['orderby']) . '" aria-selected="' . ($i === 0 ? 'true' : 'false') . '">' . esc_html($tab['title']) . '</button></li>';
}
echo '</ul>';
foreach ($tabs as $i => $tab) {
$cls = $i === 0 ? ' is-active' : '';
$loaded = $i === 0 ? '1' : '0';
echo '<div class="aksrt-ftab__panel widget-tab-posts__panel' . esc_attr($cls) . '" data-index="' . (int) $i . '" data-loaded="' . esc_attr($loaded) . '"' . ($i === 0 ? '' : ' hidden') . ' role="tabpanel">';
if ($i === 0) {
$payload = WidgetForumHotTopics::query_payload([
'count' => $count,
'orderby' => $tab['orderby'],
'period' => 'all',
'style' => $style,
'plate' => $plate,
'paginate' => 'none',
], 1);
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader></div>';
}
echo '</div>';
}
echo '</div>';
?>
<script>
(function(){
var root=document.getElementById(<?php echo wp_json_encode($uid); ?>);
if(!root||root._aksrtFtab)return; root._aksrtFtab=1;
var rest=root.getAttribute('data-rest')||'';
var count=root.getAttribute('data-count')||'6';
var style=root.getAttribute('data-style')||'mini';
var plate=root.getAttribute('data-plate')||'0';
var btns=[].slice.call(root.querySelectorAll('.aksrt-ftab__btn'));
var panels=[].slice.call(root.querySelectorAll('.aksrt-ftab__panel'));
function load(panel, orderby){
if(!panel||panel.getAttribute('data-loaded')==='1'||!rest)return;
var qs=new URLSearchParams({page:'1',count:count,orderby:orderby||'date',style:style,plate:plate,period:'all',paginate:'none'});
var url=rest+(rest.indexOf('?')>-1?'&':'?')+qs.toString();
panel.classList.add('is-ajax-loading');
fetch(url,{credentials:'same-origin',headers:{Accept:'application/json','X-WP-Nonce':(window.aksrtData&&aksrtData.restNonce)||''}})
.then(function(r){return r.json()}).then(function(j){
var d=(j&&j.data)||j||{};
var html=d.html||'';
panel.innerHTML=html||'<div class="ajax-item aksrt-ftab__empty muted-2-color">暂无内容</div>';
panel.setAttribute('data-loaded','1');
panel.classList.remove('is-ajax-loading');
}).catch(function(){panel.classList.remove('is-ajax-loading');});
}
function go(i){
btns.forEach(function(b,idx){
var on=idx===i;
b.classList.toggle('is-active',on);
b.setAttribute('aria-selected',on?'true':'false');
});
panels.forEach(function(p,idx){
var on=idx===i;
p.classList.toggle('is-active',on);
if(on){p.removeAttribute('hidden');}else{p.setAttribute('hidden','');}
});
var btn=btns[i], panel=panels[i];
if(btn&&panel) load(panel, btn.getAttribute('data-orderby')||'date');
}
btns.forEach(function(b){ b.addEventListener('click', function(){ go(parseInt(b.getAttribute('data-index')||'0',10)); }); });
})();
</script>
<?php
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param mixed $raw
* @return list<array{title:string,orderby:string}>
*/
private function normalize_tabs($raw): array {
$out = [];
if (is_string($raw)) {
foreach (array_filter(explode("\n", $raw)) as $line) {
$parts = array_map('trim', explode('|', $line));
if ($parts[0] === '') {
continue;
}
$out[] = [
'title' => $parts[0],
'orderby' => sanitize_key($parts[1] ?? 'date'),
];
}
return $out;
}
if (is_array($raw)) {
foreach ($raw as $tab) {
if (!is_array($tab)) {
continue;
}
$title = trim((string) ($tab['title'] ?? ''));
if ($title === '') {
continue;
}
$out[] = [
'title' => $title,
'orderby' => sanitize_key((string) ($tab['orderby'] ?? 'date')),
];
}
}
return $out;
}
public function form($instance): void {
$defaults = [
'title' => __('论坛栏目', 'aksrt'),
'count' => 6,
'style' => 'mini',
'plate' => 0,
'tabs' => "最新|date\n热门|views\n精华|essence",
];
$instance = wp_parse_args($instance, $defaults);
$tabs_val = $instance['tabs'];
if (is_array($tabs_val)) {
$lines = [];
foreach ($tabs_val as $t) {
$lines[] = ($t['title'] ?? '') . '|' . ($t['orderby'] ?? 'date');
}
$tabs_val = implode("\n", $lines);
}
?>
<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((string) $instance['title']); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('每栏数量', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="20" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) $instance['count']); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('样式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('style')); ?>">
<option value="mini" <?php selected($instance['style'], 'mini'); ?>><?php esc_html_e('迷你信息行', 'aksrt'); ?></option>
<option value="rank" <?php selected($instance['style'], 'rank'); ?>><?php esc_html_e('排行榜', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('版块 ID(可选)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('plate')); ?>" value="<?php echo esc_attr((string) ((int) $instance['plate'])); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('栏目(每行: 标题|排序 date/views/essence/comments)', 'aksrt'); ?>
<textarea class="widefat" rows="4" name="<?php echo esc_attr($this->get_field_name('tabs')); ?>"><?php echo esc_textarea((string) $tabs_val); ?></textarea>
</label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$style = sanitize_key((string) ($new_instance['style'] ?? 'mini'));
if (!in_array($style, ['mini', 'rank'], true)) {
$style = 'mini';
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 6))),
'style' => $style,
'plate' => max(0, (int) ($new_instance['plate'] ?? 0)),
'tabs' => sanitize_textarea_field((string) ($new_instance['tabs'] ?? '')),
];
}
}