首先创建页面模板,添加样式
<?php
/**
* Template Name: Post Filter Page
*
* Create this file in your theme directory as filter-template.php
*/
get_header(); ?>
<div class="container">
<div class="filter-page-wrapper">
<h1 class="page-title"><?php the_title(); ?></h1>
<!-- Filter Form -->
<form id="post-filter-form" class="filter-form">
<div class="filter-row">
<!-- Price Range Filter -->
<div class="filter-group">
<label for="chengce">层次:</label>
<select name="chengci" id="chengci">
<option value="">所有层次</option>
<option value="中专">中专</option>
<option value="中职">中职</option>
<option value="技校">技校</option>
</select>
</div>
<div class="filter-group">
<label for="fanwei">范围:</label>
<select name="fanwei" id="fanwei">
<option value="">所有范围</option>
<option value="全国">全国初中、高中、中职应届生</option>
<option value="广东">广东初中、高中、中职应届生</option>
</select>
</div>
<div class="filter-group">
<label for="lishu">级别:</label>
<select name="jibie" id="jibie">
<option value="">所有级别</option>
<option value="国家示范">国家示范</option>
<option value="省重点">省重点</option>
</select>
</div>
<div class="filter-group">
<label for="lishu">隶属:</label>
<select name="lishu" id="lishu">
<option value="">全部</option>
<option value="广州市教育局">广州市教育局</option>
<option value="广东省教育厅">广东省教育厅</option>
</select>
</div>
<div class="filter-group">
<label for="xiaozhi">校址:</label>
<select name="xiaozhi" id="xiaozhi">
<option value="">全部</option>
<option value="增城区">增城区</option>
<option value="天河区">天河区</option>
<option value="琶洲区">琶洲区</option>
</select>
</div>
<div class="filter-group">
<label for="xingzhi">性质:</label>
<select name="xingzhi" id="xingzhi">
<option value="">全部</option>
<option value="公办">公办</option>
<option value="民办">民办</option>
</select>
</div>
</div>
<div class="filter-row">
<!-- Category Filter -->
<div class="filter-group">
<label for="category">Category:</label>
<select name="category" id="category">
<option value="">All Categories</option>
<?php
$categories = get_categories();
foreach($categories as $category) {
echo '<option value="' . esc_attr($category->term_id) . '">' .
esc_html($category->name) . '</option>';
}
?>
</select>
</div>
</div>
<!-- Sort Filter -->
<div class="filter-row">
<div class="filter-group">
<label for="sort">Sort By:</label>
<select name="sort" id="sort">
<option value="date-desc">Newest First</option>
<option value="date-asc">Oldest First</option>
<!-- <option value="price-asc">Price: Low to High</option>
<option value="price-desc">Price: High to Low</option> -->
</select>
</div>
</div>
<?php
wp_nonce_field('post_filter_nonce', 'filter_nonce'); ?>
<div class="filter-buttons">
<button type="submit" class="filter-submit">Apply Filters</button>
<button type="reset" id="reset-filters" class="filter-reset">Reset Filters</button>
</div>
</form>
<!-- Results Container -->
<div id="filter-results">
<!-- Results will be loaded here via AJAX -->
<div class="loading-spinner" style="display: none;">
<div class="spinner"></div>
</div>
</div>
<!-- Pagination Container -->
<div id="filter-pagination" class="pagination-wrapper"></div>
</div>
</div>
<?php get_footer(); ?>
执行后端程序
<?php
// Enqueue necessary scripts and styles
function custom_filter_enqueue_scripts() {
// Enqueue styles
wp_enqueue_style('filter-styles', get_template_directory_uri() . '/assets/css/filter-styles.css');
wp_enqueue_style( 'theme-style', get_template_directory_uri() . '/css/index.css' );
wp_enqueue_style( 'admin-style', get_template_directory_uri() . '/css/yzipi-admin.css' );
wp_enqueue_style( 'pc-style', get_template_directory_uri() . '/css/yzipi-pc.css' );
wp_enqueue_style( 'mobile-style', get_template_directory_uri() . '/css/yzipi-phone.css' );
wp_enqueue_style( 'index-mobile-style', get_template_directory_uri() . '/index/index-phone.css' );
wp_enqueue_style( 'index-pc-style', get_template_directory_uri() . '/index/index-pc.css' );
// Enqueue scripts
wp_enqueue_script('jquery');
wp_enqueue_script(
'filter-script',
get_template_directory_uri() . '/assets/js/filter-script.js',
array('jquery'),
'1.0',
true
);
// Pass AJAX URL to script
wp_localize_script('filter-script', 'filterAjax', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('filter_posts_nonce')
));
}
add_action('wp_enqueue_scripts', 'custom_filter_enqueue_scripts');
// AJAX handler for filtering posts
function handle_post_filter() {
// Verify nonce
check_ajax_referer('post_filter_nonce', 'filter_nonce');
// Initialize query arguments
$args = array(
'post_type' => 'post',
'posts_per_page' => 10,
'paged' => isset($_POST['page']) ? absint($_POST['page']) : 1,
'meta_query' => array('relation' => 'AND'),
'tax_query' => array()
);
// Handle price range
// if (!empty($_POST['price_range'])) {
// $price_range = explode('-', $_POST['price_range']);
// if (count($price_range) == 2) {
// $args['meta_query'][] = array(
// 'key' => 'price',
// 'value' => array($price_range[0], $price_range[1]),
// 'type' => 'NUMERIC',
// 'compare' => 'BETWEEN'
// );
// } elseif (strpos($_POST['price_range'], '+') !== false) {
// $min_price = intval($price_range[0]);
// $args['meta_query'][] = array(
// 'key' => 'price',
// 'value' => $min_price,
// 'type' => 'NUMERIC',
// 'compare' => '>='
// );
// }
// }
// Handle location input 输入
// if (!empty($_POST['cengci'])) {
// $args['meta_query'][] = array(
// 'key' => 'cengci',
// 'value' => sanitize_text_field($_POST['cengci']),
// 'compare' => 'LIKE'
// );
// }
// cengci
if (!empty($_POST['chengci'])) {
$args['meta_query'][] = array(
'key' => 'chengci',
'value' => sanitize_text_field($_POST['chengci']),
'compare' => '='
);
}
if (!empty($_POST['fanwei'])) {
$args['meta_query'][] = array(
'key' => 'fanwei',
'value' => sanitize_text_field($_POST['fanwei']),
'compare' => '='
);
}
// jibie
if (!empty($_POST['jibie'])) {
$args['meta_query'][] = array(
'key' => 'jibie',
'value' => sanitize_text_field($_POST['jibie']),
'compare' => '='
);
}
// lishu
if (!empty($_POST['lishu'])) {
$args['meta_query'][] = array(
'key' => 'lishu',
'value' => sanitize_text_field($_POST['lishu']),
'compare' => '='
);
}
// xingzhi
if (!empty($_POST['xingzhi'])) {
$args['meta_query'][] = array(
'key' => 'xingzhi',
'value' => sanitize_text_field($_POST['xingzhi']),
'compare' => '='
);
}
//xiaozhi
if (!empty($_POST['xiaozhi'])) {
$args['meta_query'][] = array(
'key' => 'xiaozhi',
'value' => sanitize_text_field($_POST['xiaozhi']),
'compare' => '='
);
}
// Handle category
if (!empty($_POST['category'])) {
$args['tax_query'][] = array(
'taxonomy' => 'category',
'field' => 'term_id',
'terms' => absint($_POST['category'])
);
}
// Handle date range
// if (!empty($_POST['date_from']) || !empty($_POST['date_to'])) {
// $date_query = array();
// if (!empty($_POST['date_from'])) {
// $date_query['after'] = sanitize_text_field($_POST['date_from']);
// }
// if (!empty($_POST['date_to'])) {
// $date_query['before'] = sanitize_text_field($_POST['date_to']);
// }
// $args['date_query'] = array($date_query);
// }
// Handle sorting
// if (!empty($_POST['sort'])) {
// switch ($_POST['sort']) {
// case 'date-asc':
// $args['orderby'] = 'date';
// $args['order'] = 'ASC';
// break;
// case 'date-desc':
// $args['orderby'] = 'date';
// $args['order'] = 'DESC';
// break;
// case 'price-asc':
// $args['meta_key'] = 'price';
// $args['orderby'] = 'meta_value_num';
// $args['order'] = 'ASC';
// break;
// case 'price-desc':
// $args['meta_key'] = 'price';
// $args['orderby'] = 'meta_value_num';
// $args['order'] = 'DESC';
// break;
// }
// }
$query = new WP_Query($args);
ob_start();
if ($query->have_posts()) {
echo '<div class="dabw"><div class="st"><div class="posts-grid fees">';
while ($query->have_posts()) {
$query->the_post();
get_template_part('template-parts/content', 'school');
}
echo '</div></div></div>';
// Generate pagination
$total_pages = $query->max_num_pages;
if ($total_pages > 1) {
echo '<div class="pagination">';
for ($i = 1; $i <= $total_pages; $i++) {
$current_class = ($i == $_POST['page']) ? ' current' : '';
echo sprintf(
'<a href="#" class="page-number%s" data-page="%d">%d</a>',
esc_attr($current_class),
$i,
$i
);
}
echo '</div>';
}
} else {
echo '<div class="no-results">';
echo '<p>No posts found matching your criteria.</p>';
echo '</div>';
}
$response = array(
'html' => ob_get_clean(),
'found_posts' => $query->found_posts,
'max_pages' => $query->max_num_pages
);
wp_reset_postdata();
wp_send_json($response);
}
add_action('wp_ajax_filter_posts', 'handle_post_filter');
add_action('wp_ajax_nopriv_filter_posts', 'handle_post_filter');
// Function to display individual filtered post
function get_filtered_post_template() {
?>
<article id="post-<?php the_ID(); ?>" <?php post_class('filtered-post'); ?>>
<?php if (has_post_thumbnail()) : ?>
<div class="post-thumbnail">
<?php the_post_thumbnail('medium'); ?>
</div>
<?php endif; ?>
<div class="post-content">
<header class="entry-header">
<h2 class="entry-title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h2>
</header>
<div class="entry-meta">
<?php
$price = get_post_meta(get_the_ID(), 'price', true);
$location = get_post_meta(get_the_ID(), 'location', true);
$status = get_post_meta(get_the_ID(), 'status', true);
if ($price) {
echo '<span class="price">$' . number_format(esc_html($price), 2) . '</span>';
}
if ($location) {
echo '<span class="location"><i class="fas fa-map-marker-alt"></i> ' .
esc_html($location) . '</span>';
}
if ($status) {
echo '<span class="status">' . esc_html(ucfirst($status)) . '</span>';
}
?>
</div>
<div class="entry-excerpt">
<?php the_excerpt(); ?>
</div>
<div class="entry-footer">
<a href="<?php the_permalink(); ?>" class="read-more">Read More</a>
</div>
</div>
</article>
<?php
}
JS
// Save this as filter-script.js in your theme's assets/js directory
jQuery(document).ready(function($) {
const filterForm = $('#post-filter-form');
const resultsContainer = $('#filter-results');
const loadingSpinner = $('.loading-spinner');
let currentRequest = null;
// Handle form submission
filterForm.on('submit', function(e) {
e.preventDefault();
loadPosts(1);
});
// Handle pagination clicks
$(document).on('click', '.page-number', function(e) {
e.preventDefault();
loadPosts($(this).data('page'));
});
// Handle reset button
$('#reset-filters').on('click', function(e) {
e.preventDefault();
filterForm[0].reset();
loadPosts(1);
});
// Function to load posts via AJAX
function loadPosts(page) {
// Show loading spinner
loadingSpinner.show();
resultsContainer.addClass('loading');
// Abort previous request if it exists
if (currentRequest != null) {
currentRequest.abort();
}
// Get form data and append page number
const formData = new FormData(filterForm[0]);
formData.append('action', 'filter_posts');
formData.append('page', page);
// Make AJAX request
currentRequest = $.ajax({
url: filterAjax.ajaxurl,
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
// Update results container
resultsContainer.html(response.html);
// Update URL with filters (optional) 这个实用
//updateURL(formData);
// Scroll to results smoothly 这个实用
$('html, body').animate({
scrollTop: resultsContainer.offset().top - 100
}, 500);
// Update results count if needed
if (response.found_posts > 0) {
$('#results-count').text(response.found_posts + ' results found');
}
},
error: function(xhr, status, error) {
resultsContainer.html('<p class="error-message">Error loading posts. Please try again.</p>');
console.error('Ajax error:', error);
},
complete: function() {
loadingSpinner.hide();
resultsContainer.removeClass('loading');
currentRequest = null;
}
});
}
// Function to update URL with filters (optional)
function updateURL(formData) {
const params = new URLSearchParams();
for (const pair of formData.entries()) {
if (pair[1]) { // Only add non-empty values
params.append(pair[0], pair[1]);
}
}
const newURL = `${window.location.pathname}?${params.toString()}`;
window.history.pushState({}, '', newURL);
}
window.addEventListener('popstate', function() {
const urlParams = new URLSearchParams(window.location.search);
// Reset form to match URL parameters
filterForm[0].reset();
for (const [key, value] of urlParams) {
const input = filterForm.find(`[name="${key}"]`);
if (input.length) {
input.val(value);
}
}
// Load posts with current URL parameters
loadPosts(urlParams.get('page') || 1);
});
// Debounce function for search inputs
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// // Handle live search for location input
// const locationInput = $('#location');
// locationInput.on('input', debounce(function() {
// if (locationInput.val().length >= 2 || locationInput.val().length === 0) {
// loadPosts(1);
// }
// }, 500));
// // Handle date range validation
// const dateFrom = $('#date_from');
// const dateTo = $('#date_to');
// dateFrom.on('change', function() {
// dateTo.attr('min', $(this).val());
// });
// dateTo.on('change', function() {
// dateFrom.attr('max', $(this).val());
// });
// Initial load with URL parameters if they exist
const initialPage = new URLSearchParams(window.location.search).get('page') || 1;
loadPosts(initialPage);
});