首先是流程- 创建ACF级联字段的后端
[scl_lock type=”email” title=”Subscribe to unlock”]Premium content[/scl_lock]
首先创建一个上级的select字段,比方说字段名字叫city,填入选择的项目的值
然后再创建一个下级的select的字段,比方说字段名字叫region, 填入为空,我们要用到Js来实现级联选择:
(function($) {
//这里匹配的逻辑就是上级字段的option的value要和这里的guangzhou这种属性的名字对应,在ACF字段里要对应的选择的格式
//是这样: guangzhou : 广州 注意广州前面都是英文输入法,包括标点
'use strict';
// Define your xianjishis data 这里可以用AI给你生成一样格式的数据
const xianjishisData = {
'guangzhou': [
{ value: 'yuexiuqu', label: '越秀区' },
{ value: 'liwanqu', label: '荔湾区' },
{ value: 'haizhuqu', label: '海珠区' },
{ value: 'tianhequ', label: '天河区' },
{ value: 'baiyunqu', label: '白云区' },
{ value: 'huangpuqu', label: '黄埔区' },
{ value: 'panyuqu', label: '番禺区' },
{ value: 'huaduqu', label: '花都区' },
{ value: 'nanshaqu', label: '南沙区' },
{ value: 'zengchengqu', label: '增城区' },
{ value: 'conghuaqu', label: '从化区' }
],
'shenzhen': [
{ value: 'luohuqu', label: '罗湖区' },
{ value: 'futianqu', label: '福田区' },
{ value: 'nanshanqu', label: '南山区' },
{ value: 'yantianqu', label: '盐田区' },
{ value: 'baoanqu', label: '宝安区' },
{ value: 'longgangqu', label: '龙岗区' },
{ value: 'longhuaqu', label: '龙华区' },
{ value: 'pingshanqu', label: '坪山区' },
{ value: 'guangmingqu', label: '光明区' }
],
'zhuhai': [
{ value: 'xiangzhouqu', label: '香洲区' },
{ value: 'doumenqu', label: '斗门区' },
{ value: 'jinwanqu', label: '金湾区' }
],
'shantou': [
{ value: 'jinpingqu', label: '金平区' },
{ value: 'longhuqu', label: '龙湖区' },
{ value: 'haojiangqu', label: '濠江区' },
{ value: 'chaoyangqu', label: '潮阳区' },
{ value: 'chaonanqu', label: '潮南区' },
{ value: 'chenghaiqu', label: '澄海区' },
{ value: 'nanaoqu', label: '南澳县' }
],
'foshan': [
{ value: 'chanchengqu', label: '禅城区' },
{ value: 'nanhaiqu', label: '南海区' },
{ value: 'shundequ', label: '顺德区' },
{ value: 'sanshuiqu', label: '三水区' },
{ value: 'gaomingqu', label: '高明区' }
],
'shaoguan': [
{ value: 'zhenjiangqu', label: '浈江区' },
{ value: 'wujiangqu', label: '武江区' },
{ value: 'qujiangqu', label: '曲江区' },
{ value: 'lechangshi', label: '乐昌市' },
{ value: 'nanxiongshi', label: '南雄市' },
{ value: 'renhuaqu', label: '仁化县' },
{ value: 'shixingqu', label: '始兴县' },
{ value: 'wengyuanqu', label: '翁源县' },
{ value: 'xinfengqu', label: '新丰县' },
{ value: 'ruyuanqu', label: '乳源瑶族自治县' }
],
'zhanjiang': [
{ value: 'chikanqu', label: '赤坎区' },
{ value: 'xiashanqu', label: '霞山区' },
{ value: 'potouqu', label: '坡头区' },
{ value: 'mazhangqu', label: '麻章区' },
{ value: 'lianjiangshi', label: '廉江市' },
{ value: 'leilingshi', label: '雷州市' },
{ value: 'wuchuanshi', label: '吴川市' },
{ value: 'suixiqu', label: '遂溪县' },
{ value: 'xuwenqu', label: '徐闻县' }
],
'zhaoqing': [
{ value: 'duanzhouqu', label: '端州区' },
{ value: 'dinghuqu', label: '鼎湖区' },
{ value: 'gaoyaoshi', label: '高要区' },
{ value: 'sihuiishi', label: '四会市' },
{ value: 'guangningqu', label: '广宁县' },
{ value: 'huaijiqu', label: '怀集县' },
{ value: 'fengkai', label: '封开县' },
{ value: 'deqingqu', label: '德庆县' }
],
'jiangmen': [
{ value: 'pengjiangqu', label: '蓬江区' },
{ value: 'jianghaiqu', label: '江海区' },
{ value: 'xinhuiishi', label: '新会区' },
{ value: 'taishanshi', label: '台山市' },
{ value: 'kaipingshi', label: '开平市' },
{ value: 'heshanshi', label: '鹤山市' },
{ value: 'enpingshi', label: '恩平市' }
],
'maoming': [
{ value: 'maonanqu', label: '茂南区' },
{ value: 'dianbaiqu', label: '电白区' },
{ value: 'gaozhoushi', label: '高州市' },
{ value: 'huazhoushi', label: '化州市' },
{ value: 'xinyishi', label: '信宜市' }
],
'huizhou': [
{ value: 'huichengqu', label: '惠城区' },
{ value: 'huiyangqu', label: '惠阳区' },
{ value: 'boluoxian', label: '博罗县' },
{ value: 'longmenxian', label: '龙门县' },
{ value: 'huidongxian', label: '惠东县' }
],
'meizhou': [
{ value: 'meijiangqu', label: '梅江区' },
{ value: 'meixianqu', label: '梅县区' },
{ value: 'xingningshi', label: '兴宁市' },
{ value: 'pingyuanxian', label: '平远县' },
{ value: 'jiaolingxian', label: '蕉岭县' },
{ value: 'dabuxian', label: '大埔县' },
{ value: 'fengshunxian', label: '丰顺县' },
{ value: 'wuhuaxian', label: '五华县' }
],
'shanwei': [
{ value: 'chengqu', label: '城区' },
{ value: 'lufengshi', label: '陆丰市' },
{ value: 'haifengxian', label: '海丰县' },
{ value: 'luhexian', label: '陆河县' }
],
'heyuan': [
{ value: 'yuanchengqu', label: '源城区' },
{ value: 'ziijnshi', label: '紫金县' },
{ value: 'longchuanxian', label: '龙川县' },
{ value: 'lianpingxian', label: '连平县' },
{ value: 'hepingxian', label: '和平县' },
{ value: 'dongyuanxian', label: '东源县' }
],
'yangjiang': [
{ value: 'jiangchengqu', label: '江城区' },
{ value: 'yangdongqu', label: '阳东区' },
{ value: 'yangchunshi', label: '阳春市' },
{ value: 'yangxixian', label: '阳西县' }
],
'qingyuan': [
{ value: 'qingchengqu', label: '清城区' },
{ value: 'qingxinqu', label: '清新区' },
{ value: 'yingdeshi', label: '英德市' },
{ value: 'lianzhoushi', label: '连州市' },
{ value: 'fogangxian', label: '佛冈县' },
{ value: 'yangshanxian', label: '阳山县' },
{ value: 'liannanxian', label: '连山壮族瑶族自治县' },
{ value: 'liannanxian', label: '连南瑶族自治县' }
],
'dongguan': [
{ value: 'dongguan', label: '东莞市' }
],
'zhongshan': [
{ value: 'zhongshan', label: '中山市' }
],
'chaozhou': [
{ value: 'xiangqiaoqu', label: '湘桥区' },
{ value: 'chaoanqu', label: '潮安区' },
{ value: 'raopingxian', label: '饶平县' }
],
'jieyang': [
{ value: 'rongchengqu', label: '榕城区' },
{ value: 'jiedongqu', label: '揭东区' },
{ value: 'puningqu', label: '普宁市' },
{ value: 'jiexixian', label: '揭西县' },
{ value: 'huilaixian', label: '惠来县' }
],
'yunfu': [
{ value: 'yunchengqu', label: '云城区' },
{ value: 'yunanqu', label: '云安区' },
{ value: 'luodingshi', label: '罗定市' },
{ value: 'xinxingxian', label: '新兴县' },
{ value: 'yunanxian', label: '郁南县' }
]
};
// Function to update xianjishi field 更新二级选择字段的数据
function updatexianjishis(dijishiValue, $xianjishiField) {
const xianjishis = xianjishisData[dijishiValue] || [];
// Clear existing options
$xianjishiField.empty();
// Add default option
$xianjishiField.append('<option value="">- 选择县级市 -</option>');
// Add new options
xianjishis.forEach(function(xianjishi) {
$xianjishiField.append(
$('<option></option>')
.attr('value', xianjishi.value)
.text(xianjishi.label)
);
});
// Trigger ACF to refresh
$xianjishiField.trigger('change');
}
// On dijishi field change 在上级字段进行更改的时候 执行更新下级选择字段值的变更
// 要注意这里选择到select的方式 div[data-name="dijishi"] select 是选择含data-name="dijishi" 属性的div 下面的select,
// 子级的任何select都选中, 如果要选择紧跟后面的select 就是 div[data-name="dijishi"]>select
$(document).on('change', 'div[data-name="dijishi"] select', function() {
const dijishiValue = $(this).val();
const $xianjishiField = $('div[data-name="xianjishi"] select');
updatexianjishis(dijishiValue, $xianjishiField);
});
// Initialize on page load 在页面加载的时候执行下级字段按照当前一级字段存在的值的数据进行变更
if (acf) {
acf.addAction('ready', function() {
const dijishiValue = $('div[data-name="dijishi"] select').val();
const $xianjishiField = $('div[data-name="xianjishi"] select');
if (dijishiValue) {
updatexianjishis(dijishiValue, $xianjishiField);
}
});
}
})(jQuery);
后台级联字段创建好之后我们要去前端进行筛选,我们用到的是wp_ajax; wp_ajax 这里有两个阶段, 首先是选择一级城市之后通过ajax获取到下级城市的信息, 然后是通过ajax获取到拥有对应字段的文章的信息:
(function($) {
'use strict';
let currentPage = 1;
// Load regions when country changes 当上级字段变更时加载下级的字段
$('#country-select').on('change', function() {
const country = $(this).val();
const $regionSelect = $('#region-select');
if (country) {
// Show loading state 展示加载的状态
$regionSelect.prop('disabled', true).html('<option value="">Loading...</option>');
// AJAX call to get regions 获取到下级字段值的ajax 请求
$.ajax({
url: filterAjax.ajaxurl,
type: 'POST',
data: {
action: 'get_regions',
country: country,
nonce: filterAjax.nonce
},
success: function(response) {
if (response.success) {
const regions = response.data;
let options = '<option value="">-- All Regions --</option>';
$.each(regions, function(value, label) {
options += '<option value="' + value + '">' + label + '</option>';
});
$regionSelect.html(options).prop('disabled', false);
}
},
error: function() {
$regionSelect.html('<option value="">Error loading regions</option>');
}
});
} else {
// Reset region select 重置下级字段的disabled
$regionSelect.html('<option value="">-- Select Country First --</option>').prop('disabled', true);
}
// Reset region value 重置下级字段的值
$regionSelect.val('');
});
// Filter posts on form submit 当表单提交时候筛选文章
$('#posts-filter-form').on('submit', function(e) {
e.preventDefault();
currentPage = 1;
filterFrontendPosts();
});
// Reset filter 重置 filter
$('#reset-filter').on('click', function() {
$('#country-select').val('');
$('#region-select').html('<option value="">-- Select Country First --</option>').prop('disabled', true).val('');
currentPage = 1;
filterFrontendPosts();
});
// Pagination click handler (delegated) 分页点击处理
$(document).on('click', '.pagination-wrapper a', function(e) {
e.preventDefault();
const url = $(this).attr('href');
const pageMatch = url.match(/paged=(\d+)/);
if (pageMatch) {
currentPage = parseInt(pageMatch[1]);
filterFrontendPosts();
// Scroll to results 滚动到结果
$('html, body').animate({
scrollTop: $('#filtered-posts').offset().top - 100
}, 500);
}
});
// Main filter function 主要的筛选的函数
function filterFrontendPosts() {
const country = $('#country-select').val();
const region = $('#region-select').val();
const $resultsContainer = $('#filtered-posts');
const $resultsCount = $('#filter-results-count');
const $spinner = $('.loading-spinner');
// Show loading state
$spinner.show();
$resultsContainer.addClass('loading');
$.ajax({
url: filterAjax.ajaxurl,
type: 'POST',
data: {
action: 'filter_frontend_posts',
country: country,
region: region,
paged: currentPage,
nonce: filterAjax.nonce
},
success: function(response) {
console.log(response);
$spinner.hide();
$resultsContainer.removeClass('loading');
if (response.success) {
$resultsContainer.html(response.data.html);
// Update results count
if (response.data.found_posts > 0) {
$resultsCount.html('<strong>' + response.data.found_posts + '</strong> posts found');
} else {
$resultsCount.html('');
}
}
},
error: function() {
$spinner.hide();
$resultsContainer.removeClass('loading').html('<p class="error">Error loading posts. Please try again.</p>');
}
});
}
// Load initial posts on page load 当页面加载执行筛选的文章
$(document).ready(function() {
filterFrontendPosts();
});
})(jQuery);
然后是后端的回调函数,要注意这里的action不要与其他有可能性的重叠,尽量取唯一的名字,不然会引起错误:
<?php
/**
* Frontend Cascading Filter Form and AJAX Handler
* Place this in your theme's functions.php
* 要注意这里验证的方式
* 在wp-config.php要开启DEBUG_LOG
*/
// 1. Enqueue scripts for frontend filtering
function enqueue_frontend_filter_scripts() {
// Only load on specific pages (adjust as needed)
if (is_page_template('template-filtered-posts.php') || is_post_type_archive('post')) {
}
wp_enqueue_script(
'frontend-cascading-filter',
get_template_directory_uri() . '/js/frontend-filter.js',
['jquery'],
'1.0.0',
true
);
// Pass AJAX URL to JavaScript
wp_localize_script('frontend-cascading-filter', 'filterAjax', [
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('filter_posts_nonce')
]);
}
add_action('wp_enqueue_scripts', 'enqueue_frontend_filter_scripts');
// 2. AJAX Handler - Get regions based on country 根据上级字段信息获取到下级
function ajax_get_regions() {
check_ajax_referer('filter_posts_nonce', 'nonce');
$country = sanitize_text_field($_POST['country']);
// Define your regions data (you can also get this from database) 定义你的 下级字段数据,也可以从数据库获取
$regions_data = [
'america' => [
'california' => 'California',
'new_york' => 'New York',
'texas' => 'Texas',
'florida' => 'Florida',
'washington' => 'Washington'
],
'united_kingdom' => [
'england' => 'England',
'scotland' => 'Scotland',
'wales' => 'Wales',
'northern_ireland' => 'Northern Ireland'
],
'canada' => [
'ontario' => 'Ontario',
'quebec' => 'Quebec',
'british_columbia' => 'British Columbia',
'alberta' => 'Alberta'
],
'australia' => [
'new_south_wales' => 'New South Wales',
'victoria' => 'Victoria',
'queensland' => 'Queensland'
]
];
$regions = isset($regions_data[$country]) ? $regions_data[$country] : [];
wp_send_json_success($regions);
}
add_action('wp_ajax_get_regions', 'ajax_get_regions');
add_action('wp_ajax_nopriv_get_regions', 'ajax_get_regions');
// 3. AJAX Handler - Filter frontend posts
function ajax_filter_frontend_posts() {
// Add error handling 加载错误处理
try {
// Verify nonce
if (!check_ajax_referer('filter_posts_nonce', 'nonce', false)) {
wp_send_json_error(['message' => 'Security check failed']);
return;
}
// Sanitize and validate inputs 过滤提交的数据
$country = isset($_POST['country']) ? sanitize_text_field($_POST['country']) : '';
$region = isset($_POST['region']) ? sanitize_text_field($_POST['region']) : '';
$paged = isset($_POST['paged']) ? absint($_POST['paged']) : 1;
// Ensure paged is at least 1 确保分页最少是1
if ($paged < 1) {
$paged = 1;
}
// Build query arguments
$args = [
'post_type' => 'post',
'posts_per_page' => 9,
'paged' => $paged,
'post_status' => 'publish',
'no_found_rows' => false, // We need this for pagination
];
// Add meta query for filtering 添加字段筛选查询
$meta_query = ['relation' => 'AND'];
// 查询出来的文章是既包含一级字段又包含二级字段的文章, 逻辑正确 , 因为后台选择的时候如果不选择 一级字段不会出现二级字段
if (!empty($country)) {
$meta_query[] = [
'key' => 'country',
'value' => $country,
'compare' => '='
];
}
if (!empty($region)) {
$meta_query[] = [
'key' => 'region',
'value' => $region,
'compare' => '='
];
}
if (count($meta_query) > 1) {
$args['meta_query'] = $meta_query;
}
$query = new WP_Query($args);
ob_start();
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
// Get ACF fields safely 如果是acf确保插件已经启用, 否则获取默认的文章的字段
$country_value = function_exists('get_field') ? get_field('country') : get_post_meta(get_the_ID(), 'country', true);
$region_value = function_exists('get_field') ? get_field('region') : get_post_meta(get_the_ID(), 'region', true);
?>
<div class="post-item">
<h3><a href="<?php echo esc_url(get_permalink()); ?>"><?php echo esc_html(get_the_title()); ?></a></h3>
<?php if (has_post_thumbnail()) : ?>
<a href="<?php echo esc_url(get_permalink()); ?>">
<?php the_post_thumbnail('medium'); ?>
</a>
<?php endif; ?>
<div class="post-meta">
<?php if ($country_value) : ?>
<span class="country"><?php echo esc_html($country_value); ?></span>
<?php endif; ?>
<?php if ($region_value) : ?>
<span class="region"><?php echo esc_html($region_value); ?></span>
<?php endif; ?>
</div>
<div class="post-excerpt">
<?php the_excerpt(); ?>
</div>
<a href="<?php echo esc_url(get_permalink()); ?>" class="read-more">Read More</a>
</div>
<?php
}
// Pagination
if ($query->max_num_pages > 1) {
echo '<div class="pagination-wrapper">';
echo paginate_links([
'total' => $query->max_num_pages,
'current' => $paged,
'format' => '?paged=%#%',
'prev_text' => '« Previous',
'next_text' => 'Next »'
]);
echo '</div>';
}
} else {
echo '<p class="no-results">No posts found matching your criteria.</p>';
}
wp_reset_postdata();
$html = ob_get_clean();
wp_send_json_success([
'html' => $html,
'found_posts' => $query->found_posts,
'max_pages' => $query->max_num_pages
]);
} catch (Exception $e) {
// Log the error
error_log('AJAX Filter Error: ' . $e->getMessage());
wp_send_json_error([
'message' => 'An error occurred while filtering posts.'
]);
}
// Always exit
wp_die();
}
add_action('wp_ajax_filter_frontend_posts', 'ajax_filter_frontend_posts');
add_action('wp_ajax_nopriv_filter_frontend_posts', 'ajax_filter_frontend_posts');
// 4. Display Filter Form Function
function display_filter_form() {
?>
<div class="posts-filter-wrapper">
<form id="posts-filter-form" class="filter-form">
<div class="filter-row">
<div class="filter-field">
<label for="country-select">Select Country:</label>
<select id="country-select" name="country">
<option value="">-- All Countries --</option>
<option value="america">America</option>
<option value="united_kingdom">United Kingdom</option>
<option value="canada">Canada</option>
<option value="australia">Australia</option>
</select>
</div>
<div class="filter-field">
<label for="region-select">Select Region/State:</label>
<select id="region-select" name="region" disabled>
<option value="">-- Select Country First --</option>
</select>
</div>
<div class="filter-actions">
<button type="submit" class="btn-filter">Filter Posts</button>
<button type="button" id="reset-filter" class="btn-reset">Reset</button>
</div>
</div>
</form>
<div id="filter-results-count" class="results-count"></div>
<div id="filtered-posts" class="posts-grid">
<!-- Posts will be loaded here via AJAX -->
<div class="loading-spinner" style="display: none;">
<span>Loading...</span>
</div>
</div>
</div>
<?php
}
// 5. Shortcode to use anywhere
function posts_filter_shortcode() {
ob_start();
display_filter_form();
return ob_get_clean();
}
add_shortcode('posts_filter', 'posts_filter_shortcode');
