feat(forum,admin): plate follow-pay MVP, sales column, order type tabs

Wire paid plate follow (admin meta + points/balance purchase + AJAX),
re-check plate gate on topics, post list sales column, and order type
subtabs; update gap matrix and admin IA map.
This commit is contained in:
kate522 committed 2026-07-15 11:00:36 +08:00
1 parent bccb14a487
commit cf6ad0e972
12 files changed
+672 -12

No files matched your search

@@ -89,8 +89,10 @@ class AKSRT_Plate_Meta_Box {
$wp_roles = $this->wp_role_options();
$icon = (string) get_post_meta($plate_id, '_aksrt_icon', true);
$sort_order = (int) get_post_meta($plate_id, '_aksrt_sort_order', true);
$follow_pay = $this->get_follow_pay_meta($plate_id);
$this->render_access_fields($allow_view, $add_limit, $view_roles, $add_roles, $wp_roles);
$this->render_follow_pay_fields($follow_pay);
$this->render_display_fields($icon, $sort_order);
$this->render_moderator_fields($moderator_ids, $roles_map, $author_id);
$this->render_applications($applications);
@@ -151,6 +153,42 @@ class AKSRT_Plate_Meta_Box {
<?php
}
/**
* @param array{enabled:bool,modo:string,price:float,points:int,vip_price:string} $follow_pay
*/
private function render_follow_pay_fields(array $follow_pay): void {
?>
<p>
<label>
<input type="checkbox" name="aksrt_plate_follow_pay_enabled" value="1" <?php checked(!empty($follow_pay['enabled'])); ?>>
<strong><?php esc_html_e('付费关注', 'aksrt'); ?></strong>
</label>
<span class="description" style="display:block;"><?php esc_html_e('开启后,未关注用户需支付才能关注(与「关注可见」配合使用)。', 'aksrt'); ?></span>
</p>
<p>
<label for="aksrt_plate_follow_pay_modo"><strong><?php esc_html_e('关注支付方式', 'aksrt'); ?></strong></label>
<select id="aksrt_plate_follow_pay_modo" name="aksrt_plate_follow_pay_modo" class="widefat">
<option value="money" <?php selected(($follow_pay['modo'] ?? 'money'), 'money'); ?>><?php esc_html_e('现金(余额)', 'aksrt'); ?></option>
<option value="points" <?php selected(($follow_pay['modo'] ?? ''), 'points'); ?>><?php esc_html_e('积分', 'aksrt'); ?></option>
</select>
</p>
<p>
<label for="aksrt_plate_follow_pay_price"><strong><?php esc_html_e('现金价格(元)', 'aksrt'); ?></strong></label>
<input type="number" step="0.01" min="0" class="widefat" name="aksrt_plate_follow_pay_price" id="aksrt_plate_follow_pay_price" value="<?php echo esc_attr((string) ($follow_pay['price'] ?? 0)); ?>">
</p>
<p>
<label for="aksrt_plate_follow_pay_points"><strong><?php esc_html_e('积分价格', 'aksrt'); ?></strong></label>
<input type="number" step="1" min="0" class="widefat" name="aksrt_plate_follow_pay_points" id="aksrt_plate_follow_pay_points" value="<?php echo esc_attr((string) (int) ($follow_pay['points'] ?? 0)); ?>">
</p>
<p>
<label for="aksrt_plate_follow_pay_vip_price"><strong><?php esc_html_e('VIP 价格(可选)', 'aksrt'); ?></strong></label>
<input type="number" step="0.01" min="0" class="widefat" name="aksrt_plate_follow_pay_vip_price" id="aksrt_plate_follow_pay_vip_price" value="<?php echo esc_attr((string) ($follow_pay['vip_price'] ?? '')); ?>" placeholder="<?php esc_attr_e('留空=不设 VIP 价;0=VIP 免费', 'aksrt'); ?>">
<span class="description"><?php esc_html_e('现金模式用元;积分模式填积分数。', 'aksrt'); ?></span>
</p>
<hr>
<?php
}
private function render_display_fields(string $icon, int $sort_order): void {
?>
<p>
@@ -280,6 +318,7 @@ class AKSRT_Plate_Meta_Box {
}
$this->save_access_meta($post_id);
$this->save_follow_pay_meta($post_id);
$this->save_display_meta($post_id);
$this->save_moderators($post_id, (int) $post->post_author);
$this->save_applications();
@@ -325,6 +364,70 @@ class AKSRT_Plate_Meta_Box {
}
}
private function save_follow_pay_meta(int $post_id): void {
$enabled = !empty($_POST['aksrt_plate_follow_pay_enabled']);
$modo = isset($_POST['aksrt_plate_follow_pay_modo'])
? sanitize_key((string) wp_unslash($_POST['aksrt_plate_follow_pay_modo']))
: 'money';
if (!in_array($modo, ['money', 'points'], true)) {
$modo = 'money';
}
$price = isset($_POST['aksrt_plate_follow_pay_price']) ? max(0, (float) wp_unslash($_POST['aksrt_plate_follow_pay_price'])) : 0;
$points = isset($_POST['aksrt_plate_follow_pay_points']) ? max(0, (int) wp_unslash($_POST['aksrt_plate_follow_pay_points'])) : 0;
// Invalid price while enabled → force off (normalize, no migration).
if ($enabled) {
if ($modo === 'points' && $points <= 0) {
$enabled = false;
}
if ($modo === 'money' && $price <= 0) {
$enabled = false;
}
}
if (!$enabled) {
delete_post_meta($post_id, '_aksrt_follow_pay');
delete_post_meta($post_id, '_aksrt_follow_pay_modo');
delete_post_meta($post_id, '_aksrt_follow_pay_price');
delete_post_meta($post_id, '_aksrt_follow_pay_points');
delete_post_meta($post_id, '_aksrt_follow_pay_vip_price');
return;
}
update_post_meta($post_id, '_aksrt_follow_pay', 1);
update_post_meta($post_id, '_aksrt_follow_pay_modo', $modo);
update_post_meta($post_id, '_aksrt_follow_pay_price', $price);
update_post_meta($post_id, '_aksrt_follow_pay_points', $points);
if (isset($_POST['aksrt_plate_follow_pay_vip_price']) && (string) wp_unslash($_POST['aksrt_plate_follow_pay_vip_price']) !== '') {
$vip = max(0, (float) wp_unslash($_POST['aksrt_plate_follow_pay_vip_price']));
update_post_meta($post_id, '_aksrt_follow_pay_vip_price', $vip);
} else {
delete_post_meta($post_id, '_aksrt_follow_pay_vip_price');
}
}
/**
* @return array{enabled:bool,modo:string,price:float,points:int,vip_price:string}
*/
private function get_follow_pay_meta(int $plate_id): array {
$enabled = (int) get_post_meta($plate_id, '_aksrt_follow_pay', true) === 1;
$modo = sanitize_key((string) (get_post_meta($plate_id, '_aksrt_follow_pay_modo', true) ?: 'money'));
if (!in_array($modo, ['money', 'points'], true)) {
$modo = 'money';
}
$vip_raw = get_post_meta($plate_id, '_aksrt_follow_pay_vip_price', true);
$vip_set = ($vip_raw !== '' && $vip_raw !== null && $vip_raw !== false);
return [
'enabled' => $enabled,
'modo' => $modo,
'price' => (float) get_post_meta($plate_id, '_aksrt_follow_pay_price', true),
'points' => (int) get_post_meta($plate_id, '_aksrt_follow_pay_points', true),
'vip_price' => $vip_set ? (string) (float) $vip_raw : '',
];
}
private function save_moderators(int $post_id, int $author_id): void {
$role_posted = isset($_POST['aksrt_plate_mod_role']) && is_array($_POST['aksrt_plate_mod_role'])
? wp_unslash($_POST['aksrt_plate_mod_role'])