feat(widgets): pricing/FAQ/stats + forum topic list and bbs sidebars
Add zibll produck widgets (pricing plans, FAQ, stats), forum topic lists, register bbs_* sidebar zones, and wire plate/topic/home/compose templates to render those widget areas.
This commit is contained in:
@@ -530,3 +530,210 @@
|
||||
color: var(--color-primary, #3b82f6);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
||||
/* ---- Pricing plans ---- */
|
||||
.aksrt-pricing { margin-bottom: 16px; }
|
||||
.aksrt-pricing__cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.aksrt-pricing__card {
|
||||
position: relative;
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
border-radius: 14px;
|
||||
padding: 22px 18px 18px;
|
||||
background: var(--surface-1, #fff);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
}
|
||||
.aksrt-pricing__card.is-featured {
|
||||
box-shadow: 0 10px 28px rgba(0,0,0,.08);
|
||||
border-color: transparent;
|
||||
border-top: 3px solid var(--plan-accent, #3b82f6);
|
||||
}
|
||||
.aksrt-pricing__card.accent-pink { --plan-accent: #ec4899; }
|
||||
.aksrt-pricing__card.accent-purple { --plan-accent: #a855f7; }
|
||||
.aksrt-pricing__card.accent-blue { --plan-accent: #3b82f6; }
|
||||
.aksrt-pricing__card.accent-green { --plan-accent: #22c55e; }
|
||||
.aksrt-pricing__card.accent-yellow { --plan-accent: #eab308; }
|
||||
.aksrt-pricing__card.accent-cyan { --plan-accent: #06b6d4; }
|
||||
.aksrt-pricing__badge {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
background: var(--plan-accent, #3b82f6);
|
||||
color: #fff;
|
||||
}
|
||||
.aksrt-pricing__name { font-size: 18px; font-weight: 700; margin: 0 0 6px; }
|
||||
.aksrt-pricing__desc { font-size: 13px; color: var(--muted-2-color, #888); margin: 0 0 12px; }
|
||||
.aksrt-pricing__price { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; }
|
||||
.aksrt-pricing__main { font-size: 32px; font-weight: 800; color: var(--plan-accent, #3b82f6); line-height: 1; }
|
||||
.aksrt-pricing__mark { font-size: .55em; margin-right: 2px; }
|
||||
.aksrt-pricing__original { font-size: 13px; color: #999; text-decoration: line-through; }
|
||||
.aksrt-pricing__features { list-style: none; margin: 0 0 16px; padding: 0; flex: 1; }
|
||||
.aksrt-pricing__features li { display: flex; gap: 8px; font-size: 13px; padding: 5px 0; }
|
||||
.aksrt-pricing__features i { color: var(--plan-accent, #3b82f6); margin-top: 2px; }
|
||||
.aksrt-pricing__btn {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 10px 14px;
|
||||
border-radius: 999px;
|
||||
background: var(--plan-accent, #3b82f6);
|
||||
color: #fff !important;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.aksrt-pricing__trust {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14px 22px;
|
||||
margin-top: 18px;
|
||||
justify-content: center;
|
||||
}
|
||||
.aksrt-pricing__trust-item { display: flex; gap: 8px; align-items: flex-start; font-size: 13px; }
|
||||
.aksrt-pricing__trust-item i { color: var(--color-primary, #3b82f6); margin-top: 2px; }
|
||||
.aksrt-pricing__trust-item strong { display: block; }
|
||||
.aksrt-pricing__trust-item span { color: var(--muted-2-color, #888); font-size: 12px; }
|
||||
|
||||
/* ---- FAQ ---- */
|
||||
.aksrt-faq { margin-bottom: 16px; }
|
||||
.aksrt-faq__search { display: flex; gap: 8px; margin-bottom: 14px; }
|
||||
.aksrt-faq__search-input {
|
||||
flex: 1;
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
.aksrt-faq__search-btn {
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
padding: 8px 14px;
|
||||
background: var(--color-primary, #3b82f6);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.aksrt-faq__item {
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
border-radius: 10px;
|
||||
padding: 0;
|
||||
margin-bottom: 10px;
|
||||
background: var(--surface-1, #fff);
|
||||
}
|
||||
.aksrt-faq__q {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 14px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
.aksrt-faq__q::-webkit-details-marker { display: none; }
|
||||
.aksrt-faq__num {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
background: var(--surface-2, #f3f4f6);
|
||||
color: var(--color-primary, #3b82f6);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.aksrt-faq__a {
|
||||
padding: 0 14px 14px 52px;
|
||||
font-size: 13px;
|
||||
color: var(--muted-2-color, #666);
|
||||
line-height: 1.6;
|
||||
}
|
||||
.aksrt-faq__aside { display: grid; gap: 12px; margin-top: 14px; }
|
||||
.aksrt-faq__doc, .aksrt-faq__help {
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
padding: 14px;
|
||||
background: var(--surface-2, #f8fafc);
|
||||
}
|
||||
.aksrt-faq__help-btn {
|
||||
display: inline-block;
|
||||
margin-top: 8px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: var(--color-primary, #3b82f6);
|
||||
color: #fff !important;
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* ---- Stats ---- */
|
||||
.aksrt-stats { margin-bottom: 16px; }
|
||||
.aksrt-stats__intro { margin-bottom: 14px; }
|
||||
.aksrt-stats__intro-title { font-size: 22px; margin: 0 0 6px; }
|
||||
.aksrt-stats__intro-sub { color: var(--muted-2-color, #888); margin: 0; font-size: 13px; }
|
||||
.aksrt-stats__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--aksrt-stats-pc, 4), minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
@media (max-width: 767.98px) {
|
||||
.aksrt-stats__grid {
|
||||
grid-template-columns: repeat(var(--aksrt-stats-m, 2), minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
.aksrt-stats__item {
|
||||
border-radius: 12px;
|
||||
padding: 16px 14px;
|
||||
background: var(--surface-1, #fff);
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
text-align: center;
|
||||
}
|
||||
.aksrt-stats__item.color-pink { --stat-c: #ec4899; }
|
||||
.aksrt-stats__item.color-purple { --stat-c: #a855f7; }
|
||||
.aksrt-stats__item.color-orange { --stat-c: #f97316; }
|
||||
.aksrt-stats__item.color-blue { --stat-c: #3b82f6; }
|
||||
.aksrt-stats__item.color-green { --stat-c: #22c55e; }
|
||||
.aksrt-stats__icon { font-size: 20px; color: var(--stat-c, #3b82f6); margin-bottom: 8px; }
|
||||
.aksrt-stats__value { font-size: 28px; font-weight: 800; color: var(--stat-c, #3b82f6); line-height: 1.1; }
|
||||
.aksrt-stats__prefix, .aksrt-stats__unit { font-size: .55em; font-weight: 600; }
|
||||
.aksrt-stats__label { margin-top: 6px; font-size: 12px; color: var(--muted-2-color, #888); }
|
||||
|
||||
/* ---- Forum topic lists ---- */
|
||||
.aksrt-ftopic-list { list-style: none; margin: 0; padding: 0; }
|
||||
.aksrt-ftopic-list__item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px dashed var(--color-line, #e5e7eb);
|
||||
}
|
||||
.aksrt-ftopic-list__item:last-child { border-bottom: 0; }
|
||||
.aksrt-ftopic-list__link {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.aksrt-ftopic-list__views {
|
||||
flex-shrink: 0;
|
||||
font-size: 11px;
|
||||
color: var(--muted-2-color, #999);
|
||||
}
|
||||
.aksrt-ftopic-list.is-detail .aksrt-ftopic-list__item { flex-direction: column; gap: 4px; }
|
||||
.aksrt-ftopic-list__meta {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
font-size: 11px;
|
||||
color: var(--muted-2-color, #999);
|
||||
}
|
||||
|
||||
/* Forum widget sidebar shell */
|
||||
.aksrt-forum-widget-zone { margin-top: 12px; }
|
||||
.aksrt-forum-widget-zone .zib-widget { margin-bottom: 14px; }
|
||||
|
||||
@@ -63,6 +63,11 @@ function aksrt_register_widgets() {
|
||||
register_widget('\AKSRT\Widgets\WidgetTextTitle');
|
||||
register_widget('\AKSRT\Widgets\WidgetUserLists');
|
||||
|
||||
register_widget('\AKSRT\Widgets\WidgetPricingPlans');
|
||||
register_widget('\AKSRT\Widgets\WidgetFaq');
|
||||
register_widget('\AKSRT\Widgets\WidgetStats');
|
||||
|
||||
|
||||
// Forum widgets
|
||||
if (class_exists('\AKSRT\Widgets\WidgetForumBoardList')) {
|
||||
register_widget('\AKSRT\Widgets\WidgetForumBoardList');
|
||||
@@ -82,6 +87,9 @@ function aksrt_register_widgets() {
|
||||
if (class_exists('\AKSRT\Widgets\WidgetForumPlateModerator')) {
|
||||
register_widget('\AKSRT\Widgets\WidgetForumPlateModerator');
|
||||
}
|
||||
if (class_exists('\AKSRT\Widgets\WidgetForumTopicLists')) {
|
||||
register_widget('\AKSRT\Widgets\WidgetForumTopicLists');
|
||||
}
|
||||
|
||||
// Shop product widgets (§6.9)
|
||||
register_widget('\AKSRT\Widgets\WidgetProductList');
|
||||
|
||||
@@ -382,6 +382,29 @@ trait SetupPart1 {
|
||||
'after_title' => $title_after,
|
||||
]);
|
||||
|
||||
// ── Forum / BBS widget areas (zibll bbs_* parity lite) ──
|
||||
$forum_widgets = [
|
||||
'bbs_home_sidebar' => [__('论坛首页 · 侧栏', 'aksrt'), __('论坛首页侧栏区域。', 'aksrt')],
|
||||
'bbs_home_top_fluid' => [__('论坛首页 · 顶部全宽', 'aksrt'), __('论坛首页顶部全宽。', 'aksrt')],
|
||||
'bbs_plate_sidebar' => [__('论坛版块 · 侧栏', 'aksrt'), __('版块页侧栏。', 'aksrt')],
|
||||
'bbs_plate_top_fluid' => [__('论坛版块 · 顶部全宽', 'aksrt'), __('版块页顶部全宽。', 'aksrt')],
|
||||
'bbs_posts_sidebar' => [__('论坛帖子 · 侧栏', 'aksrt'), __('帖子页侧栏挂件区。', 'aksrt')],
|
||||
'bbs_posts_top_fluid' => [__('论坛帖子 · 顶部全宽', 'aksrt'), __('帖子页顶部全宽。', 'aksrt')],
|
||||
'bbs_new_posts_sidebar_top' => [__('论坛发帖 · 侧栏顶部', 'aksrt'), __('发帖页侧栏顶部。', 'aksrt')],
|
||||
'bbs_new_posts_sidebar_bottom' => [__('论坛发帖 · 侧栏底部', 'aksrt'), __('发帖页侧栏底部。', 'aksrt')],
|
||||
];
|
||||
foreach ($forum_widgets as $sid => $meta) {
|
||||
register_sidebar([
|
||||
'id' => $sid,
|
||||
'name' => $meta[0],
|
||||
'description' => $meta[1],
|
||||
'before_widget' => $card_wrapper_before,
|
||||
'after_widget' => '</div>',
|
||||
'before_title' => $title_before,
|
||||
'after_title' => $title_after,
|
||||
]);
|
||||
}
|
||||
|
||||
// ── Shop widget areas (§6.9) ──
|
||||
$shop_widgets = [
|
||||
'shop_product_top_fluid' => ['商品详情页 · 顶部通栏', '商品详情页顶部全宽区域。'],
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
/**
|
||||
* FAQ accordion (zibll zib_widget_faq lite).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetFaq extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_faq',
|
||||
esc_html__('AKSRT 常见问题', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('可折叠 FAQ 列表,支持搜索框与侧栏文档卡。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_faq',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$faqs = $this->normalize_faqs($instance['faqs'] ?? []);
|
||||
if (!$faqs) {
|
||||
return;
|
||||
}
|
||||
|
||||
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-faq-' . esc_attr($this->id);
|
||||
echo '<div class="aksrt-faq" id="' . $uid . '">';
|
||||
|
||||
if (!empty($instance['search_s'])) {
|
||||
$ph = (string) ($instance['search_placeholder'] ?? __('搜索问题…', 'aksrt'));
|
||||
echo '<form class="aksrt-faq__search" method="get" action="' . esc_url(home_url('/')) . '" role="search">';
|
||||
echo '<input type="search" name="s" class="aksrt-faq__search-input" placeholder="' . esc_attr($ph) . '" />';
|
||||
echo '<button type="submit" class="aksrt-faq__search-btn">' . esc_html((string) ($instance['search_btn_text'] ?? __('搜索', 'aksrt'))) . '</button>';
|
||||
echo '</form>';
|
||||
}
|
||||
|
||||
echo '<div class="aksrt-faq__list">';
|
||||
$n = 0;
|
||||
foreach ($faqs as $f) {
|
||||
$n++;
|
||||
$num = str_pad((string) $n, 2, '0', STR_PAD_LEFT);
|
||||
echo '<details class="aksrt-faq__item">';
|
||||
echo '<summary class="aksrt-faq__q"><span class="aksrt-faq__num">' . esc_html($num) . '</span><span class="aksrt-faq__qtext">' . esc_html($f['question']) . '</span></summary>';
|
||||
if ($f['answer'] !== '') {
|
||||
echo '<div class="aksrt-faq__a">' . wp_kses_post(wpautop($f['answer'])) . '</div>';
|
||||
}
|
||||
echo '</details>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
$doc_title = trim((string) ($instance['doc_title'] ?? ''));
|
||||
$doc_desc = trim((string) ($instance['doc_desc'] ?? ''));
|
||||
$help_title = trim((string) ($instance['help_title'] ?? ''));
|
||||
$help_url = esc_url_raw(trim((string) ($instance['help_btn_url'] ?? '')));
|
||||
$help_btn = trim((string) ($instance['help_btn_text'] ?? ''));
|
||||
if ($doc_title !== '' || $help_title !== '') {
|
||||
echo '<div class="aksrt-faq__aside">';
|
||||
if ($doc_title !== '') {
|
||||
echo '<div class="aksrt-faq__doc"><strong>' . esc_html($doc_title) . '</strong>';
|
||||
if ($doc_desc !== '') {
|
||||
echo '<p>' . esc_html($doc_desc) . '</p>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
if ($help_title !== '') {
|
||||
echo '<div class="aksrt-faq__help"><strong>' . esc_html($help_title) . '</strong>';
|
||||
$sub = trim((string) ($instance['help_subtitle'] ?? ''));
|
||||
if ($sub !== '') {
|
||||
echo '<p>' . esc_html($sub) . '</p>';
|
||||
}
|
||||
if ($help_btn !== '' && $help_url !== '') {
|
||||
echo '<a class="aksrt-faq__help-btn" href="' . esc_url($help_url) . '">' . esc_html($help_btn) . '</a>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array{question:string,answer:string}>
|
||||
*/
|
||||
private function normalize_faqs($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$q = sanitize_text_field((string) ($row['question'] ?? $row['q'] ?? ''));
|
||||
if ($q === '') {
|
||||
continue;
|
||||
}
|
||||
$a = (string) ($row['answer'] ?? $row['a'] ?? '');
|
||||
// Allow basic HTML in answers after kses on output.
|
||||
$out[] = [
|
||||
'question' => $q,
|
||||
'answer' => wp_kses_post($a),
|
||||
];
|
||||
if (count($out) >= 30) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$faqs = is_string($instance['faqs'] ?? null)
|
||||
? (string) $instance['faqs']
|
||||
: wp_json_encode($this->normalize_faqs($instance['faqs'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<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('FAQ JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('faqs')); ?>" placeholder='[{"question":"如何付费?","answer":"进入用户中心…"}]'><?php echo esc_textarea((string) $faqs); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('search_s')); ?>" value="1" <?php checked(!empty($instance['search_s'])); ?>> <?php esc_html_e('显示搜索框', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('文档卡标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('doc_title')); ?>" value="<?php echo esc_attr((string) ($instance['doc_title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('文档卡描述', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('doc_desc')); ?>" value="<?php echo esc_attr((string) ($instance['doc_desc'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('帮助卡标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('help_title')); ?>" value="<?php echo esc_attr((string) ($instance['help_title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('帮助按钮文案', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('help_btn_text')); ?>" value="<?php echo esc_attr((string) ($instance['help_btn_text'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('帮助按钮链接', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('help_btn_url')); ?>" value="<?php echo esc_attr((string) ($instance['help_btn_url'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'faqs' => $this->normalize_faqs($new_instance['faqs'] ?? []),
|
||||
'search_s' => !empty($new_instance['search_s']) ? 1 : 0,
|
||||
'search_placeholder' => sanitize_text_field((string) ($new_instance['search_placeholder'] ?? '')),
|
||||
'search_btn_text' => sanitize_text_field((string) ($new_instance['search_btn_text'] ?? '')),
|
||||
'doc_title' => sanitize_text_field((string) ($new_instance['doc_title'] ?? '')),
|
||||
'doc_desc' => sanitize_text_field((string) ($new_instance['doc_desc'] ?? '')),
|
||||
'help_title' => sanitize_text_field((string) ($new_instance['help_title'] ?? '')),
|
||||
'help_subtitle' => sanitize_text_field((string) ($new_instance['help_subtitle'] ?? '')),
|
||||
'help_btn_text' => sanitize_text_field((string) ($new_instance['help_btn_text'] ?? '')),
|
||||
'help_btn_url' => esc_url_raw(trim((string) ($new_instance['help_btn_url'] ?? ''))),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
/**
|
||||
* Forum topic list widget (zibll zib_bbs_widget_ui_posts_lists lite).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetForumTopicLists extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_forum_topic_lists',
|
||||
esc_html__('论坛 - 帖子列表', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('按排序/版块筛选展示论坛帖子。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_forum_topic_lists',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
if (!post_type_exists('aksrt_forum_topic')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$count = max(1, min(30, (int) ($instance['count'] ?? 8)));
|
||||
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
|
||||
$style = sanitize_key((string) ($instance['style'] ?? 'mini'));
|
||||
$plate_id = (int) ($instance['plate_id'] ?? 0);
|
||||
$current_plate = !empty($instance['current_plate']);
|
||||
|
||||
if ($current_plate) {
|
||||
$resolved = $this->resolve_current_plate_id();
|
||||
if ($resolved <= 0) {
|
||||
return;
|
||||
}
|
||||
$plate_id = $resolved;
|
||||
}
|
||||
|
||||
$query = [
|
||||
'post_type' => 'aksrt_forum_topic',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'order' => 'DESC',
|
||||
];
|
||||
|
||||
switch ($orderby) {
|
||||
case 'views':
|
||||
$query['meta_key'] = '_aksrt_views';
|
||||
$query['orderby'] = 'meta_value_num';
|
||||
break;
|
||||
case 'comments':
|
||||
$query['orderby'] = 'comment_count';
|
||||
break;
|
||||
case 'modified':
|
||||
$query['orderby'] = 'modified';
|
||||
break;
|
||||
default:
|
||||
$query['orderby'] = 'date';
|
||||
}
|
||||
|
||||
if ($plate_id > 0) {
|
||||
$query['meta_query'] = [[
|
||||
'key' => '_aksrt_plate_id',
|
||||
'value' => $plate_id,
|
||||
]];
|
||||
}
|
||||
|
||||
$q = new \WP_Query($query);
|
||||
if (!$q->have_posts()) {
|
||||
wp_reset_postdata();
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$list_class = $style === 'detail' ? 'aksrt-ftopic-list is-detail' : 'aksrt-ftopic-list is-mini';
|
||||
echo '<ul class="' . esc_attr($list_class) . '">';
|
||||
while ($q->have_posts()) {
|
||||
$q->the_post();
|
||||
$views = (int) get_post_meta(get_the_ID(), '_aksrt_views', true);
|
||||
echo '<li class="aksrt-ftopic-list__item">';
|
||||
echo '<a class="aksrt-ftopic-list__link" href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . '</a>';
|
||||
if ($style === 'detail') {
|
||||
echo '<div class="aksrt-ftopic-list__meta">';
|
||||
echo '<span>' . esc_html(get_the_author()) . '</span>';
|
||||
echo '<span>' . esc_html(get_the_date()) . '</span>';
|
||||
echo '<span><i class="fas fa-eye"></i> ' . esc_html((string) $views) . '</span>';
|
||||
echo '</div>';
|
||||
} else {
|
||||
echo '<span class="aksrt-ftopic-list__views">' . esc_html((string) $views) . '</span>';
|
||||
}
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
wp_reset_postdata();
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
private function resolve_current_plate_id(): int {
|
||||
if (is_singular('aksrt_forum_plate')) {
|
||||
return (int) get_queried_object_id();
|
||||
}
|
||||
if (is_singular('aksrt_forum_topic')) {
|
||||
$topic_id = (int) get_queried_object_id();
|
||||
$plate_id = (int) get_post_meta($topic_id, '_aksrt_plate_id', true);
|
||||
return max(0, $plate_id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<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'] ?? __('最新帖子', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('数量', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="30" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 8))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('发布时间', 'aksrt'); ?></option>
|
||||
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览量', 'aksrt'); ?></option>
|
||||
<option value="comments" <?php selected(($instance['orderby'] ?? ''), 'comments'); ?>><?php esc_html_e('评论数', 'aksrt'); ?></option>
|
||||
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?php esc_html_e('更新时间', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</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'), 'mini'); ?>><?php esc_html_e('紧凑', 'aksrt'); ?></option>
|
||||
<option value="detail" <?php selected(($instance['style'] ?? ''), 'detail'); ?>><?php esc_html_e('详情', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('限定版块 ID(0=全部)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('plate_id')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['plate_id'] ?? 0))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('current_plate')); ?>" value="1" <?php checked(!empty($instance['current_plate'])); ?>> <?php esc_html_e('仅当前版块(版块/帖子页)', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
|
||||
if (!in_array($orderby, ['date', 'views', 'comments', 'modified'], true)) {
|
||||
$orderby = 'date';
|
||||
}
|
||||
return [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'count' => max(1, min(30, (int) ($new_instance['count'] ?? 8))),
|
||||
'orderby' => $orderby,
|
||||
'style' => ($new_instance['style'] ?? '') === 'detail' ? 'detail' : 'mini',
|
||||
'plate_id' => max(0, (int) ($new_instance['plate_id'] ?? 0)),
|
||||
'current_plate' => !empty($new_instance['current_plate']) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
/**
|
||||
* Pricing plan cards (zibll zib_widget_pricing_plans lite).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetPricingPlans extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_pricing_plans',
|
||||
esc_html__('AKSRT 价格方案', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('多列定价卡片 + 信任条(落地页/VIP 介绍)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_pricing_plans',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$plans = $this->normalize_plans($instance['lists'] ?? $instance['plans'] ?? []);
|
||||
if (!$plans) {
|
||||
return;
|
||||
}
|
||||
$trust = $this->normalize_trust($instance['trust_items'] ?? []);
|
||||
$currency = sanitize_text_field((string) ($instance['currency'] ?? '¥'));
|
||||
if ($currency === '') {
|
||||
$currency = '¥';
|
||||
}
|
||||
|
||||
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-pricing">';
|
||||
echo '<div class="aksrt-pricing__cards">';
|
||||
foreach ($plans as $plan) {
|
||||
$accent = sanitize_key((string) ($plan['color'] ?? 'blue'));
|
||||
if (!in_array($accent, ['pink', 'purple', 'blue', 'green', 'yellow', 'cyan'], true)) {
|
||||
$accent = 'blue';
|
||||
}
|
||||
$featured = !empty($plan['is_featured']);
|
||||
$class = 'aksrt-pricing__card accent-' . $accent . ($featured ? ' is-featured' : '');
|
||||
echo '<div class="' . esc_attr($class) . '">';
|
||||
if ($featured || !empty($plan['tag'])) {
|
||||
$badge = (string) ($plan['tag'] !== '' ? $plan['tag'] : __('推荐', 'aksrt'));
|
||||
echo '<span class="aksrt-pricing__badge">' . esc_html($badge) . '</span>';
|
||||
}
|
||||
echo '<h3 class="aksrt-pricing__name">' . esc_html($plan['name']) . '</h3>';
|
||||
if ($plan['desc'] !== '') {
|
||||
echo '<p class="aksrt-pricing__desc">' . esc_html($plan['desc']) . '</p>';
|
||||
}
|
||||
$price = (float) $plan['price'];
|
||||
$show = (float) $plan['show_price'];
|
||||
echo '<div class="aksrt-pricing__price">';
|
||||
echo '<span class="aksrt-pricing__main"><span class="aksrt-pricing__mark">' . esc_html($currency) . '</span>' . esc_html($this->fmt($price)) . '</span>';
|
||||
if ($show > $price && $show > 0) {
|
||||
echo '<span class="aksrt-pricing__original"><span class="aksrt-pricing__mark">' . esc_html($currency) . '</span>' . esc_html($this->fmt($show)) . '</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
if ($plan['features']) {
|
||||
echo '<ul class="aksrt-pricing__features">';
|
||||
foreach ($plan['features'] as $ft) {
|
||||
echo '<li><i class="fa fa-check" aria-hidden="true"></i><span>' . esc_html($ft) . '</span></li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
if ($plan['btn_text'] !== '' && $plan['btn_url'] !== '') {
|
||||
$url = $plan['btn_url'];
|
||||
if (!empty($instance['go_link']) && function_exists('aksrt_go_link')) {
|
||||
$url = aksrt_go_link($url, true);
|
||||
}
|
||||
echo '<div class="aksrt-pricing__action"><a class="aksrt-pricing__btn" href="' . esc_url($url) . '">' . esc_html($plan['btn_text']) . '</a></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
if ($trust) {
|
||||
echo '<div class="aksrt-pricing__trust">';
|
||||
foreach ($trust as $ti) {
|
||||
echo '<div class="aksrt-pricing__trust-item">';
|
||||
if ($ti['icon'] !== '') {
|
||||
echo '<i class="' . esc_attr($ti['icon']) . '" aria-hidden="true"></i>';
|
||||
}
|
||||
echo '<div><strong>' . esc_html($ti['title']) . '</strong>';
|
||||
if ($ti['desc'] !== '') {
|
||||
echo '<span>' . esc_html($ti['desc']) . '</span>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
private function fmt(float $n): string {
|
||||
if (abs($n - round($n)) < 0.001) {
|
||||
return (string) (int) round($n);
|
||||
}
|
||||
return number_format($n, 2, '.', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array<string,mixed>>
|
||||
*/
|
||||
private function normalize_plans($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$name = sanitize_text_field((string) ($row['name'] ?? $row['title'] ?? ''));
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
$intro = (string) ($row['intro'] ?? $row['features'] ?? '');
|
||||
if (is_array($row['features'] ?? null)) {
|
||||
$features = array_values(array_filter(array_map('sanitize_text_field', $row['features'])));
|
||||
} else {
|
||||
$features = array_values(array_filter(array_map('sanitize_text_field', preg_split('/\r\n|\r|\n/', $intro) ?: [])));
|
||||
}
|
||||
$btn = is_array($row['btn'] ?? null) ? $row['btn'] : [];
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
|
||||
'price' => (float) ($row['price'] ?? 0),
|
||||
'show_price' => (float) ($row['show_price'] ?? 0),
|
||||
'color' => sanitize_key((string) ($row['color'] ?? 'blue')),
|
||||
'is_featured' => !empty($row['is_featured']) ? 1 : 0,
|
||||
'tag' => sanitize_text_field((string) ($row['tag'] ?? '')),
|
||||
'features' => $features,
|
||||
'btn_text' => sanitize_text_field((string) ($row['btn_text'] ?? $btn['text'] ?? '')),
|
||||
'btn_url' => esc_url_raw(trim((string) ($row['btn_url'] ?? $btn['url'] ?? ''))),
|
||||
];
|
||||
if (count($out) >= 6) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array{title:string,desc:string,icon:string}>
|
||||
*/
|
||||
private function normalize_trust($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$title = sanitize_text_field((string) ($row['title'] ?? ''));
|
||||
if ($title === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'title' => $title,
|
||||
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
|
||||
'icon' => sanitize_text_field((string) ($row['icon'] ?? 'fa fa-check-circle')),
|
||||
];
|
||||
if (count($out) >= 6) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$plans = is_string($instance['lists'] ?? null)
|
||||
? (string) $instance['lists']
|
||||
: wp_json_encode($this->normalize_plans($instance['lists'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$trust = is_string($instance['trust_items'] ?? null)
|
||||
? (string) $instance['trust_items']
|
||||
: wp_json_encode($this->normalize_trust($instance['trust_items'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<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="text" name="<?php echo esc_attr($this->get_field_name('currency')); ?>" value="<?php echo esc_attr((string) ($instance['currency'] ?? '¥')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('方案 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('lists')); ?>" placeholder='[{"name":"Pro","price":99,"show_price":199,"desc":"适合创作者","features":["无限下载","优先支持"],"btn_text":"立即开通","btn_url":"#","is_featured":1,"color":"pink"}]'><?php echo esc_textarea((string) $plans); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('信任条 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="3" name="<?php echo esc_attr($this->get_field_name('trust_items')); ?>"><?php echo esc_textarea((string) $trust); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'currency' => sanitize_text_field((string) ($new_instance['currency'] ?? '¥')),
|
||||
'lists' => $this->normalize_plans($new_instance['lists'] ?? []),
|
||||
'trust_items' => $this->normalize_trust($new_instance['trust_items'] ?? []),
|
||||
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
/**
|
||||
* Stats number cards (zibll zib_widget_stats lite).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetStats extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_stats',
|
||||
esc_html__('AKSRT 数据统计', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('数字统计卡片网格(站点数据展示)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_stats',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$stats = $this->normalize_stats($instance['stats'] ?? []);
|
||||
if (!$stats) {
|
||||
return;
|
||||
}
|
||||
$cols = max(1, min(4, (int) ($instance['columns'] ?? 4)));
|
||||
$m_cols = max(1, min(3, (int) ($instance['mobile_columns'] ?? 2)));
|
||||
$intro_title = trim((string) ($instance['intro_title'] ?? ''));
|
||||
$intro_sub = trim((string) ($instance['intro_subtitle'] ?? ''));
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$style = sprintf('--aksrt-stats-pc:%d;--aksrt-stats-m:%d;', $cols, $m_cols);
|
||||
echo '<div class="aksrt-stats" style="' . esc_attr($style) . '">';
|
||||
if ($intro_title !== '' || $intro_sub !== '') {
|
||||
echo '<div class="aksrt-stats__intro">';
|
||||
if ($intro_title !== '') {
|
||||
echo '<h2 class="aksrt-stats__intro-title">' . esc_html($intro_title) . '</h2>';
|
||||
}
|
||||
if ($intro_sub !== '') {
|
||||
echo '<p class="aksrt-stats__intro-sub">' . esc_html($intro_sub) . '</p>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '<div class="aksrt-stats__grid">';
|
||||
$palette = ['pink', 'purple', 'orange', 'blue', 'green'];
|
||||
$i = 0;
|
||||
foreach ($stats as $s) {
|
||||
$color = sanitize_key((string) ($s['color'] ?? 'auto'));
|
||||
if ($color === '' || $color === 'auto') {
|
||||
$color = $palette[$i % count($palette)];
|
||||
}
|
||||
$i++;
|
||||
echo '<div class="aksrt-stats__item color-' . esc_attr($color) . '">';
|
||||
if ($s['icon'] !== '') {
|
||||
echo '<div class="aksrt-stats__icon"><i class="' . esc_attr($s['icon']) . '" aria-hidden="true"></i></div>';
|
||||
}
|
||||
echo '<div class="aksrt-stats__value">';
|
||||
if ($s['prefix'] !== '') {
|
||||
echo '<span class="aksrt-stats__prefix">' . esc_html($s['prefix']) . '</span>';
|
||||
}
|
||||
echo '<span class="aksrt-stats__number" data-target="' . esc_attr($s['number']) . '">' . esc_html($s['number']) . '</span>';
|
||||
if ($s['unit'] !== '') {
|
||||
echo '<span class="aksrt-stats__unit">' . esc_html($s['unit']) . '</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
if ($s['label'] !== '') {
|
||||
echo '<div class="aksrt-stats__label">' . esc_html($s['label']) . '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array{number:string,label:string,unit:string,prefix:string,icon:string,color:string}>
|
||||
*/
|
||||
private function normalize_stats($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$number = sanitize_text_field((string) ($row['number'] ?? ''));
|
||||
if ($number === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'number' => $number,
|
||||
'label' => sanitize_text_field((string) ($row['label'] ?? '')),
|
||||
'unit' => sanitize_text_field((string) ($row['unit'] ?? '')),
|
||||
'prefix' => sanitize_text_field((string) ($row['prefix'] ?? '')),
|
||||
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
|
||||
'color' => sanitize_key((string) ($row['color'] ?? 'auto')),
|
||||
];
|
||||
if (count($out) >= 12) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$stats = is_string($instance['stats'] ?? null)
|
||||
? (string) $instance['stats']
|
||||
: wp_json_encode($this->normalize_stats($instance['stats'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<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('数据 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('stats')); ?>" placeholder='[{"number":"1280","label":"用户","unit":"+","icon":"fas fa-users","color":"blue"}]'><?php echo esc_textarea((string) $stats); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('PC 列数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['columns'] ?? 4))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('移动列数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="3" name="<?php echo esc_attr($this->get_field_name('mobile_columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['mobile_columns'] ?? 2))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('介绍标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('intro_title')); ?>" value="<?php echo esc_attr((string) ($instance['intro_title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('介绍副标题', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('intro_subtitle')); ?>"><?php echo esc_textarea((string) ($instance['intro_subtitle'] ?? '')); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'stats' => $this->normalize_stats($new_instance['stats'] ?? []),
|
||||
'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 4))),
|
||||
'mobile_columns' => max(1, min(3, (int) ($new_instance['mobile_columns'] ?? 2))),
|
||||
'intro_title' => sanitize_text_field((string) ($new_instance['intro_title'] ?? '')),
|
||||
'intro_subtitle' => sanitize_text_field((string) ($new_instance['intro_subtitle'] ?? '')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -436,6 +436,19 @@ $initial_content = $draft_data ? $draft_data['content'] : '';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (is_active_sidebar('bbs_new_posts_sidebar_top') || is_active_sidebar('bbs_new_posts_sidebar_bottom')) : ?>
|
||||
<div class="aksrt-forum-widget-zone">
|
||||
<?php
|
||||
if (is_active_sidebar('bbs_new_posts_sidebar_top')) {
|
||||
dynamic_sidebar('bbs_new_posts_sidebar_top');
|
||||
}
|
||||
if (is_active_sidebar('bbs_new_posts_sidebar_bottom')) {
|
||||
dynamic_sidebar('bbs_new_posts_sidebar_bottom');
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div><!-- /.aksrt-forum-compose__sidebar -->
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -56,8 +56,9 @@ $max_pages = (int) ($view['pagination']['max_pages'] ?? 1);
|
||||
?>
|
||||
|
||||
<main id="main-content" class="site-main content-shell content-shell--stream content-shell--forum" tabindex="-1">
|
||||
<?php if (is_active_sidebar('bbs_plate_top_fluid')) { echo '<div class="aksrt-fluid-widget aksrt-fluid-widget--forum">'; dynamic_sidebar('bbs_plate_top_fluid'); echo '</div>'; } ?>
|
||||
<div class="content-wrap container">
|
||||
<div class="content-layout content-layout--no-sidebar">
|
||||
<div class="content-layout content-layout--with-sidebar">
|
||||
<div class="content-layout__primary">
|
||||
|
||||
<?php if (function_exists('aksrt_breadcrumbs')) { aksrt_breadcrumbs(); } ?>
|
||||
@@ -318,6 +319,11 @@ $max_pages = (int) ($view['pagination']['max_pages'] ?? 1);
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
<?php if (is_active_sidebar('bbs_plate_sidebar')) : ?>
|
||||
<aside class="content-layout__sidebar aksrt-forum-widget-zone">
|
||||
<?php dynamic_sidebar('bbs_plate_sidebar'); ?>
|
||||
</aside>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -412,6 +412,11 @@ $comments_enabled = !empty($permissions['can_reply']);
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php if (is_active_sidebar('bbs_posts_sidebar')) : ?>
|
||||
<div class="aksrt-forum-widget-zone">
|
||||
<?php dynamic_sidebar('bbs_posts_sidebar'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</aside>
|
||||
|
||||
<div class="aksrt-forum-report-modal" id="aksrt-report-modal" hidden>
|
||||
|
||||
@@ -348,6 +348,11 @@ if (empty($public_home_tabs)) {
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (is_active_sidebar('bbs_home_sidebar')) : ?>
|
||||
<div class="aksrt-forum-widget-zone">
|
||||
<?php dynamic_sidebar('bbs_home_sidebar'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user