You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Alpine.js报错:total未定义,WordPress分类加载文章异常

解决Alpine.js "total is not defined" 错误

问题概述

开发了一个展示WordPress文章、支持分类筛选加载的程序,目前控制台持续报错:

Alpine Expression Error: total is not defined

对应触发表达式为"total > (limit + offset)",以下是当前完整代码:

<?php /* Template Name: test */ 
?>
<html>
<head>
     <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
     <script>

document.addEventListener('alpine:init', () => {

Alpine.data("filterPosts", (adminURL) => ({
    posts: null,
    category: null,
    showDefault: true,
    showFiltered: false,
    loadingNew: false, // <--- defaults to false
    loadingMore: false, // <--- defaults to false
    offset: 0,

    filterPosts(id) {
        this.loadingNew = true;
        this.showDefault = false;
        this.showFiltered = true;
        this.category = id;
        this.offset = 0; // <-- reset offset to zero
        this.fetchPosts();
    },
    
    loadMore() {
        this.loadingMore = true;
        this.offset += 10;
        this.showFiltered = true;
        this.fetchPosts(true);
    },

    fetchPosts(append = false) {
        var formData = new FormData();
        formData.append("action", "filterPosts");
        formData.append("offset", this.offset); // <-- Add new data to the form
        // formData.append("limit", this.limit);

        if (this.category) {
            formData.append("category", this.category);
        }

        fetch(adminURL, {
            method: "POST",
            body: formData,
        })
        
      .then((res) => res.json())
        .then((res) => {
            if (append) {
                // Appends posts to the end of existing posts data
                this.posts = this.posts.concat(res.posts);
            } else {
                // Resets the posts data with new data
                this.posts = res.posts;
            }
            
                this.loading = false;
             // Reset both loading states
            // this.loadingNew = false;
            // this.loadingMore = false;
         
        });
    },
    
    getTotal() {
    var formData = new FormData();
    formData.append("action", "filterPosts");

    fetch(adminURL, {
        method: "POST",
        body: formData,
    })
    .then((res) => res.json())
    .then((res) => {
        this.total = res.total;
    });
},

init() {
    this.getTotal();
}
}));
})

    
     </script>
</head>
<body>
    <div x-data="filterPosts('<?php echo admin_url('admin-ajax.php'); ?>')">
        
        
<section <?php theme_section_attr_id() ?> <?php theme_section_attr_class('main-section js-posts') ?>>
  <div class="container">
    <div class="heading mx-0">
      <?php $before_title = get_sub_field('before_title');
      if ($before_title) : ?>
        <strong class="sub-title"><?php echo $before_title ?></strong>
      <?php endif ?>
      <?php $title = get_sub_field('title');
      if ($title) : ?>
        <h2><?php echo $title ?></h2>
      <?php endif ?>
    </div>

      <!--<div class="head js-posts-search-text">-->
      <!--  <?php if ($search_value) : ?>-->
      <!--    <h2 class="h5 fw-semibold"><?php printf(__('Showing results for “%s”', 'base'), $search_value) ?></h2>-->
      <!--  <?php endif ?>-->
      <!--</div>-->
      
<!--alipne js dynamic post start-->
    <div class="has-filter row flex-md-row-reverse">
        <div class="col-md-8 col-lg-9 js-posts-holder">
                <!-- Posts Column -->
            <div x-show.important="showDefault" class="row cards-area js-posts-items">
                <!-- Default posts query -->
                <?php get_template_part( 'template-parts/posts-filter/default-query' ); ?>
                 <!-- Load More Posts -->
                    <div class="text-center">
                        <button 
                            @click="loadMore()" 
                            x-show="total > (limit + offset)"
                            class="bg-white border border-solid border-slate-200 hover:bg-slate-100 px-4 py-2">
                            Load More</button>
                    </div>
            </div>
           
            
                <!-- Filtered posts -->
            <div x-show.important="showFiltered" x-html="posts" class="row cards-area js-posts-items">
                <!-- Default posts query -->
            </div>
            
            <!-- Skeleton Loader -->
    <template x-if="loadingNew || loadingMore">
        <div class="grid grid-cols-2 gap-6">
            <template x-for="i in 4">
                <div>
                    <div class="h-52 w-full bg-slate-200 rounded-lg"></div>
                    <div class="h-8 w-1/2 bg-slate-200 rounded-lg mt-4"></div>
                </div>
            </template>
        </div>
    </template>
    </div>
            
        <!-- Filtered posts -->
        <div class="col-md-4 col-lg-3">
             <aside class="sidebar">
                 <div class="widget widget-filter">
                     <button class="widget-title">Explore Topics</button>
                     <svg class="separator" viewBox="0 0 238 11" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
                <line opacity="0.3" y1="0.5" x2="101.942" y2="0.5" stroke="#3CD5AF"></line>
                <line opacity="0.3" y1="10.5" x2="237.5" y2="10.5" stroke="#3CD5AF"></line>
              </svg>
            <?php 
                $categories = get_categories();
                if ( ! empty( $categories ) ) : 
            ?>
            <ul class="filter-list">
                <?php foreach ( $categories as $category ) :
                
                // Skip "Uncategorized" category
                if ($category->name == 'Uncategorized') continue; ?>    
        
                   <li :class=" category == <?php echo $category->term_id; ?> ? '' : ''">
            <a hreff="" @click="filterPosts(<?= $category->term_id; ?>)"><?= esc_html( $category->name ); ?></button>
        </li>
                
                <?php endforeach; ?>
            </ul>
            <?php endif; ?>
            </div>
</aside>
        <!-- Filtered end -->
          </div>   
    </div>
    </div>
<!--alipne js dynamic post end-->

</section>
</div>

</body>
</html>

错误原因

  1. 未初始化核心变量:Alpine.data的初始属性中未声明total和limit,DOM渲染时无法找到这两个变量。
  2. 异步加载时序问题:getTotal()是异步请求,页面初始渲染时total还未被赋值,触发未定义错误。
  3. 代码逻辑遗漏:
    • fetchPosts中使用了未定义的this.loading,应该改为控制loadingNew和loadingMore。
    • 分类链接存在标签不匹配(<a>对应</button>)和hreff拼写错误。

修复方案

1. 初始化缺失的变量

在Alpine.data的初始属性中添加total和limit:

Alpine.data("filterPosts", (adminURL) => ({
    posts: null,
    category: null,
    showDefault: true,
    showFiltered: false,
    loadingNew: false,
    loadingMore: false,
    offset: 0,
    total: 0, // 初始化total为0
    limit: 10, // 初始化分页数量,根据需求调整
    // ... 其他方法
}));

2. 修复按钮显示的表达式

添加空值判断,避免初始渲染时的错误:

<button 
    @click="loadMore()" 
    x-show="total !== 0 && total > (limit + offset)"
    class="bg-white border border-solid border-slate-200 hover:bg-slate-100 px-4 py-2">
    Load More
</button>

3. 修复fetchPosts中的loading状态

取消注释并修正loading状态的控制:

.then((res) => {
    if (append) {
        this.posts = this.posts.concat(res.posts);
    } else {
        this.posts = res.posts;
    }
    // 修复loading状态
    this.loadingNew = false;
    this.loadingMore = false;
});

4. 修复分类链接的语法错误

修正标签不匹配和拼写问题,添加@click.prevent阻止默认跳转:

<li :class="category == <?php echo $category->term_id; ?> ? 'active' : ''">
    <a href="#" @click.prevent="filterPosts(<?= $category->term_id; ?>)"><?= esc_html( $category->name ); ?></a>
</li>

5. 启用limit参数(可选)

如果需要传递limit到后端,取消fetchPosts中对应代码的注释:

formData.append("limit", this.limit);

内容的提问来源于stack exchange,提问作者Beep

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 18:37:04